Design: Tolong Dukung Label Sewenang-wenang dan Gotos.

Dibuat pada 8 Sep 2016  ·  159Komentar  ·  Sumber: WebAssembly/design

Saya ingin menunjukkan bahwa saya belum pernah terlibat dalam upaya perakitan web,
dan saya tidak memelihara kompiler besar atau banyak digunakan (hanya milik saya sendiri
bahasa mainan, kontribusi kecil pada backend kompiler QBE, dan
magang di tim kompiler IBM), tetapi saya akhirnya menjadi agak kasar, dan
didorong untuk berbagi lebih luas.

Jadi, sementara saya agak tidak nyaman melompat dan menyarankan perubahan besar
untuk proyek yang belum saya kerjakan ... begini:

Keluhan Saya:

Saat saya menulis kompiler, hal pertama yang saya lakukan dengan level tinggi
struktur -- loop, pernyataan if, dan seterusnya -- memvalidasinya untuk semantik,
melakukan pengecekan tipe dan sebagainya. Hal kedua yang saya lakukan dengan mereka adalah membuangnya
keluar, dan ratakan ke blok dasar, dan mungkin ke bentuk SSA. Di beberapa bagian lain
dari dunia kompiler, format populer adalah gaya penerusan lanjutan. saya tidak
seorang ahli dalam kompilasi dengan gaya passing lanjutan, tetapi sepertinya tidak
cocok untuk loop dan blok cakupan yang tampaknya dimiliki oleh rakitan web
memeluk.

Saya ingin berpendapat bahwa format berbasis goto yang lebih datar akan jauh lebih berguna karena
target untuk pengembang kompiler, dan tidak akan secara signifikan menghalangi
penulisan polyfill yang dapat digunakan.

Secara pribadi, saya juga bukan penggemar berat ekspresi kompleks bersarang. Mereka sedikit
lebih kikuk untuk dikonsumsi, terutama jika simpul bagian dalam dapat memiliki efek samping, tetapi saya
jangan terlalu menolak mereka sebagai pelaksana kompiler -- Majelis web
JIT dapat mengkonsumsinya, saya dapat mengabaikannya dan menghasilkan instruksi yang memetakan
ke IR saya. Mereka tidak membuat saya ingin membalik meja.

Masalah yang lebih besar turun ke loop, blok, dan elemen sintaksis lainnya
bahwa, sebagai penulis kompiler yang mengoptimalkan, Anda berusaha sangat keras untuk mewakili sebagai
grafik dengan cabang mewakili tepi; Konstruksi aliran kontrol eksplisit
adalah penghalang. Rekonstruksi mereka dari grafik setelah Anda benar-benar selesai
pengoptimalan yang Anda inginkan tentu saja mungkin, tetapi ini sedikit
kompleksitas untuk bekerja di sekitar format yang lebih kompleks. Dan itu mengganggu saya: Keduanya
produsen dan konsumen sedang mengatasi masalah yang sepenuhnya diciptakan
yang akan dihindari dengan hanya menjatuhkan konstruksi aliran kontrol yang kompleks
dari perakitan web.

Selain itu, desakan konstruksi tingkat yang lebih tinggi menyebabkan beberapa
kasus patologis. Misalnya, Perangkat Duff berakhir dengan web yang mengerikan
keluaran perakitan, seperti yang terlihat dengan bermain-main di The Wasm Explorer .
Namun, kebalikannya tidak benar: Segala sesuatu yang dapat diungkapkan
di web assembler dapat dengan mudah dikonversi ke yang setara di beberapa
tidak terstruktur, format berbasis goto.

Jadi, setidaknya, saya ingin menyarankan agar tim perakitan web menambahkan
dukungan untuk label dan gotos sewenang-wenang. Jika mereka memilih untuk mempertahankan yang lebih tinggi
konstruksi level, itu akan menjadi sedikit kerumitan yang sia-sia, tapi setidaknya
penulis kompiler seperti saya akan dapat mengabaikannya dan menghasilkan keluaran
secara langsung.

pengisian poli:

Salah satu kekhawatiran yang saya dengar ketika membahas ini adalah bahwa loop
dan struktur berbasis blok memungkinkan polyfilling perakitan web yang lebih mudah.
Meskipun ini tidak sepenuhnya salah, saya pikir itu solusi polyfill sederhana
untuk label dan gotos dimungkinkan. Meskipun mungkin tidak cukup optimal,
Saya pikir itu sepadan dengan sedikit keburukan dalam bytecode secara berurutan
untuk menghindari memulai alat baru dengan utang teknis bawaan.

Jika kita mengasumsikan LLVM (atau QBE) seperti sintaks untuk perakitan web, maka beberapa kode
yang terlihat seperti:

int f(int x) {
    if (x == 42)
        return 123;
    else
        return 666;
}

mungkin dikompilasi ke:

 func @f(%x : i32) {
    %1 = test %x 42
jmp %1 iftrue iffalse

 L0:
    %r =i 123
jmp LRet
 L1:
    %r =i 666
jmp LRet
 Lret:
    ret %r
 }

Ini bisa di-polyfill ke Javascript yang terlihat seperti:

function f(x) {
    var __label = L0;
    var __ret;

    while (__label != LRet) {
        switch (__label) {
        case L0:
            var _v1 = (x == 42)
            if (_v1) {__lablel = L1;} else {label = L2;}
            break;
        case L1:
            __ret = 123
            __label = LRet
            break;
        case L2;
            __ret = 666
            __label = LRet
            break;
        default:
            assert(false);
            break;
    }
}

Apakah itu jelek? Ya. Apakah itu penting? Semoga, jika perakitan web lepas landas,
tidak lama.

Dan jika tidak:

Nah, jika saya pernah menargetkan perakitan web, saya kira saya akan menghasilkan kode
menggunakan pendekatan yang saya sebutkan di polyfill, dan melakukan yang terbaik untuk mengabaikan semua
konstruksi tingkat tinggi, berharap bahwa kompiler akan cukup pintar untuk
menangkap pola ini.

Tetapi alangkah baiknya jika kita tidak perlu memiliki kedua sisi pembuatan kode
bekerja di sekitar format yang ditentukan.

control flow

Komentar yang paling membantu

Rilis Go 1.11 mendatang akan memiliki dukungan eksperimental untuk WebAssembly. Ini akan mencakup dukungan penuh untuk semua fitur Go, termasuk goroutine, saluran, dll. Namun, kinerja WebAssembly yang dihasilkan saat ini tidak begitu bagus.

Ini terutama karena instruksi goto yang hilang. Tanpa instruksi goto, kami harus menggunakan loop tingkat atas dan tabel lompat di setiap fungsi. Menggunakan algoritme relooper bukanlah pilihan bagi kami, karena ketika berpindah antar goroutine, kami harus dapat melanjutkan eksekusi pada titik yang berbeda dari suatu fungsi. Relooper tidak dapat membantu dengan ini, hanya instruksi goto yang bisa.

Sungguh luar biasa bahwa WebAssembly sampai pada titik di mana ia dapat mendukung bahasa seperti Go. Tetapi untuk benar-benar menjadi perakitan web, WebAssembly harus sama kuatnya dengan bahasa rakitan lainnya. Go memiliki kompiler canggih yang mampu memancarkan perakitan yang sangat efisien untuk sejumlah platform lain. Inilah sebabnya mengapa saya ingin berargumen bahwa ini terutama merupakan batasan WebAssembly dan bukan kompiler Go sehingga tidak mungkin juga menggunakan kompiler ini untuk memancarkan Majelis yang efisien untuk web.

Semua 159 komentar

@oridb Wasm agak dioptimalkan bagi konsumen untuk dapat dengan cepat mengonversi ke bentuk SSA, dan strukturnya membantu di sini untuk pola kode umum, sehingga struktur tidak selalu menjadi beban bagi konsumen. Saya tidak setuju dengan pernyataan Anda bahwa 'kedua sisi pembuatan kode bekerja di sekitar format yang ditentukan'. Wasm sangat banyak tentang konsumen ramping dan cepat, dan jika Anda memiliki beberapa proposal untuk membuatnya lebih ramping dan lebih cepat maka itu mungkin konstruktif.

Blok yang dapat diurutkan menjadi DAG dapat diekspresikan dalam blok dan cabang wasm, seperti contoh Anda. Switch-loop adalah gaya yang digunakan saat diperlukan, dan mungkin konsumen dapat melakukan jump threading untuk membantu di sini. Mungkin lihat binaryen yang mungkin melakukan banyak pekerjaan untuk backend kompiler Anda.

Ada permintaan lain untuk dukungan CFG yang lebih umum, dan beberapa pendekatan lain menggunakan loop yang disebutkan, tetapi mungkin fokusnya ada di tempat lain saat ini.

Saya tidak berpikir ada rencana untuk mendukung 'gaya penerusan lanjutan' secara eksplisit dalam pengkodean, tetapi telah disebutkan tentang argumen yang muncul dari blok dan loop (seperti lambda) dan mendukung banyak nilai (beberapa argumen lambda) dan menambahkan a pick operator untuk mempermudah referensi definisi (argumen lambda).

strukturnya membantu di sini untuk pola kode umum

Saya tidak melihat pola kode umum yang lebih mudah untuk diwakili dalam hal cabang ke label arbitrer, vs loop terbatas dan subset blok yang ditegakkan oleh perakitan web. Saya bisa melihat manfaat kecil jika ada upaya untuk membuat kode sangat mirip dengan kode input untuk kelas bahasa tertentu, tetapi itu tampaknya bukan tujuan - dan konstruksinya agak kosong jika ada di sana

Blok yang dapat diurutkan menjadi DAG dapat diekspresikan dalam blok dan cabang wasm, seperti contoh Anda.

Ya, mereka bisa. Namun, saya sangat memilih untuk tidak menambahkan pekerjaan ekstra untuk menentukan mana yang dapat direpresentasikan dengan cara ini, versus mana yang membutuhkan pekerjaan ekstra. Secara realistis, saya akan melewatkan melakukan analisis ekstra, dan selalu hanya menghasilkan bentuk loop sakelar.

Sekali lagi, argumen saya bukanlah bahwa loop dan blok membuat segalanya menjadi tidak mungkin; Itu karena semua yang dapat mereka lakukan lebih sederhana dan lebih mudah bagi mesin untuk menulis dengan goto, goto_if, dan label tidak terstruktur yang sewenang-wenang.

Mungkin lihat binaryen yang mungkin melakukan banyak pekerjaan untuk backend kompiler Anda.

Saya sudah memiliki backend yang dapat diservis yang cukup saya sukai, dan berencana untuk sepenuhnya mem-bootstrap seluruh kompiler dalam bahasa saya sendiri. Saya lebih suka tidak menambahkan ketergantungan ekstra yang agak besar hanya untuk mengatasi penggunaan loop/blok yang dipaksakan. Jika saya hanya menggunakan loop switch, memancarkan kode cukup sepele. Jika saya mencoba untuk benar-benar menggunakan fitur-fitur yang ada di perakitan web secara efektif, alih-alih melakukan yang terbaik untuk berpura-pura tidak ada, itu menjadi jauh lebih tidak menyenangkan.

Ada permintaan lain untuk dukungan CFG yang lebih umum, dan beberapa pendekatan lain menggunakan loop yang disebutkan, tetapi mungkin kekuatannya ada di tempat lain saat ini.

Saya masih tidak yakin bahwa loop memiliki manfaat apa pun -- apa pun yang dapat direpresentasikan dengan loop dapat direpresentasikan dengan goto dan label, dan ada konversi cepat dan terkenal ke SSA dari daftar instruksi datar.

Sejauh CPS berjalan, saya tidak berpikir bahwa perlu ada dukungan eksplisit -- ini populer di kalangan FP karena cukup mudah untuk mengkonversi ke perakitan secara langsung, dan memberikan manfaat yang mirip dengan SSA dalam hal penalaran (http:// mlton.org/pipermail/mlton/2003-Januari/023054.html); Sekali lagi, saya bukan ahlinya, tetapi dari apa yang saya ingat, kelanjutan doa diturunkan ke label, beberapa gerakan, dan goto.

@oridb 'ada konversi cepat dan terkenal ke SSA dari daftar instruksi datar'

Akan menarik untuk mengetahui bagaimana mereka membandingkan dengan decoder SSA wasm, itu pertanyaan penting?

Wasm memanfaatkan tumpukan nilai saat ini, dan beberapa manfaat dari itu akan hilang tanpa struktur, itu akan merusak kinerja dekoder. Tanpa tumpukan nilai, decoding SSA akan memiliki lebih banyak pekerjaan juga, saya sudah mencoba kode dasar register dan decoding lebih lambat (tidak yakin seberapa signifikan itu).

Apakah Anda akan menyimpan tumpukan nilai, atau menggunakan desain berbasis register? Jika menjaga tumpukan nilai maka mungkin itu menjadi klon CIL, dan mungkin kinerja wasm dapat dibandingkan dengan CIL, adakah yang benar-benar memeriksa ini?

Apakah Anda akan menyimpan tumpukan nilai, atau menggunakan desain berbasis register?

Saya tidak benar-benar memiliki perasaan yang kuat di akhir itu. Saya membayangkan kekompakan pengkodean akan menjadi salah satu perhatian terbesar; Desain register mungkin tidak berjalan dengan baik di sana -- atau mungkin berubah menjadi kompres secara fantastis melalui gzip. Aku tidak benar-benar tahu dari atas kepalaku.

Performa adalah masalah lain, meskipun saya menduga itu mungkin kurang penting mengingat kemampuan untuk men-cache keluaran biner, ditambah fakta bahwa waktu pengunduhan mungkin lebih besar daripada decoding dengan urutan besarnya.

Akan menarik untuk mengetahui bagaimana mereka membandingkan dengan decoder SSA wasm, itu pertanyaan penting?

Jika Anda mendekode ke SSA, itu menyiratkan bahwa Anda juga akan melakukan pengoptimalan dalam jumlah yang wajar. Saya ingin tahu untuk membandingkan seberapa signifikan kinerja decoding di tempat pertama. Tapi, ya, itu pasti pertanyaan yang bagus.

Terima kasih atas pertanyaan dan kekhawatiran Anda.

Perlu dicatat bahwa banyak perancang dan pelaksana dari
WebAssembly memiliki latar belakang kinerja tinggi, JIT industri, tidak hanya
untuk JavaScript (V8, SpiderMonkey, Chakra, dan JavaScriptCore), tetapi juga di
LLVM dan kompiler lainnya. Saya pribadi telah menerapkan dua JIT untuk Java
bytecode dan saya dapat membuktikan bahwa mesin tumpukan dengan gotos tidak terbatas
memperkenalkan beberapa kompleksitas dalam decoding, verifikasi, dan membangun a
IR penyusun. Sebenarnya ada banyak pola yang bisa diekspresikan dalam Java
bytecode yang akan menyebabkan JIT kinerja tinggi, termasuk C1 dan C2 di
HotSpot untuk menyerah begitu saja dan menurunkan kode untuk hanya berjalan di
penerjemah. Sebaliknya, membangun IR kompiler dari sesuatu seperti an
AST dari JavaScript atau bahasa lain adalah sesuatu yang juga telah saya lakukan. Itu
struktur ekstra AST membuat beberapa pekerjaan ini jauh lebih sederhana.

Desain konstruksi aliran kontrol WebAssembly menyederhanakan konsumen dengan
memungkinkan verifikasi cepat dan sederhana, konversi satu pass yang mudah ke formulir SSA
(bahkan grafik IR), JIT single-pass yang efektif, dan (dengan postorder dan
stack machine) interpretasi di tempat yang relatif sederhana. Tersusun
kontrol membuat grafik aliran kontrol yang tidak dapat direduksi menjadi tidak mungkin, yang menghilangkan
seluruh kelas kasus sudut buruk untuk dekoder dan kompiler. Juga
dengan baik mengatur panggung untuk penanganan pengecualian dalam bytecode WASM, yang V8
sudah mengembangkan prototipe bersamaan dengan produksi
penerapan.

Kami telah melakukan banyak diskusi internal antara anggota tentang hal ini
topik, karena, untuk bytecode, itu adalah satu hal yang paling berbeda dari
target tingkat mesin lainnya. Namun, itu tidak berbeda dengan penargetan
bahasa sumber seperti JavaScript (yang dilakukan banyak kompiler saat ini) dan
hanya membutuhkan sedikit reorganisasi blok untuk mencapai struktur. Di sana
dikenal algoritma untuk melakukan ini, dan alat. Kami ingin memberikan beberapa
panduan yang lebih baik bagi para produsen yang memulai dengan CFG sewenang-wenang untuk
berkomunikasi ini lebih baik. Untuk bahasa yang menargetkan WASM langsung dari AST
(yang sebenarnya adalah sesuatu yang V8 lakukan sekarang untuk kode asm.js--langsung
menerjemahkan byte JavaScript AST ke WASM), tidak ada restrukturisasi
langkah yang diperlukan. Kami berharap ini menjadi kasus untuk banyak alat bahasa
di seluruh spektrum yang tidak memiliki IR canggih di dalamnya.

Pada Kam, 8 Sep 2016 jam 09:53, Ori Bernstein [email protected]
menulis:

Apakah Anda akan menyimpan tumpukan nilai, atau menggunakan desain berbasis register?

Saya tidak benar-benar memiliki perasaan yang kuat di akhir itu. Saya akan membayangkan
kekompakan pengkodean akan menjadi salah satu perhatian terbesar; Seperti kamu
disebutkan, kinerja adalah hal lain.

Akan menarik untuk mengetahui bagaimana mereka membandingkan dengan decoder SSA wasm, itu
adalah pertanyaan penting?

Jika Anda mendekode ke SSA, itu berarti Anda juga akan melakukan
jumlah optimasi yang wajar. Saya ingin tahu untuk membandingkan caranya
kinerja decoding yang signifikan adalah di tempat pertama. Tapi, ya, itu
pasti pertanyaan yang bagus.


Anda menerima ini karena Anda berlangganan utas ini.
Balas email ini secara langsung, lihat di GitHub
https://github.com/WebAssembly/design/issues/796#issuecomment -245521009,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/ALnq1Iz1nn4--NL32R9ev0JPKfEnDyvqks5qn77cgaJpZM4J3ofA
.

Terima kasih @titzer , saya mengembangkan kecurigaan bahwa struktur Wasm memiliki tujuan yang lebih dari sekadar kesamaan dengan asm.js. Saya bertanya-tanya: Java bytecode (dan CIL) tidak memodelkan CFG atau tumpukan nilai secara langsung, mereka harus disimpulkan oleh JIT. Tetapi di Wasm (terutama jika tanda tangan blok ditambahkan) JIT dapat dengan mudah mengetahui apa yang terjadi dengan tumpukan nilai dan aliran kontrol, jadi saya bertanya-tanya, apakah CFG (atau aliran kontrol yang tidak dapat direduksi secara khusus) dimodelkan secara eksplisit seperti loop dan blok, mungkinkah itu menghindari sebagian besar kasus sudut buruk yang Anda pikirkan?

Ada pengoptimalan rapi yang digunakan penafsir yang mengandalkan aliran kontrol yang tidak dapat direduksi untuk meningkatkan prediksi cabang...

@oridb

Saya ingin berpendapat bahwa format berbasis goto yang lebih datar akan jauh lebih berguna karena
target untuk pengembang kompiler

Saya setuju bahwa gotos sangat berguna untuk banyak kompiler. Itu sebabnya alat seperti Binaryen memungkinkan Anda menghasilkan CFG sewenang-wenang dengan gotos , dan mereka dapat mengonversinya dengan sangat cepat dan efisien menjadi WebAssembly untuk Anda.

Mungkin membantu untuk menganggap WebAssembly sebagai hal yang dioptimalkan untuk dikonsumsi browser (seperti yang ditunjukkan oleh @titzer ). Sebagian besar kompiler mungkin tidak menghasilkan WebAssembly secara langsung, melainkan menggunakan alat seperti Binaryen, sehingga mereka dapat mengeluarkan gotos, mendapatkan banyak pengoptimalan secara gratis, dan tidak perlu memikirkan detail format biner tingkat rendah dari WebAssembly (sebagai gantinya Anda memancarkan IR menggunakan API sederhana).

Mengenai polyfilling dengan pola while-switch yang Anda sebutkan: di emscripten kami memulai seperti itu sebelum kami mengembangkan metode "relooper" untuk membuat ulang loop. Pola while-switch rata-rata sekitar 4x lebih lambat (tetapi dalam beberapa kasus secara signifikan kurang atau lebih, misalnya loop kecil lebih sensitif). Saya setuju dengan Anda bahwa secara teori, optimisasi jump-threading dapat mempercepatnya, tetapi kinerjanya akan kurang dapat diprediksi karena beberapa VM akan melakukannya lebih baik daripada yang lain. Ini juga secara signifikan lebih besar dalam hal ukuran kode.

Mungkin membantu untuk menganggap WebAssembly sebagai hal yang dioptimalkan untuk dikonsumsi browser (seperti yang ditunjukkan oleh @titzer ). Sebagian besar kompiler mungkin tidak menghasilkan WebAssembly secara langsung, melainkan menggunakan alat seperti Binaryen...

Saya masih tidak yakin bahwa aspek ini akan sangat berarti - sekali lagi, saya menduga biaya pengambilan bytecode akan mendominasi penundaan yang dilihat pengguna, dengan biaya terbesar kedua adalah optimasi yang dilakukan, dan bukan penguraian dan validasi . Saya juga berasumsi/berharap bahwa bytecode akan dibuang, dan output yang dikompilasi adalah apa yang akan di-cache, membuat kompilasi secara efektif menjadi biaya satu kali.

Tetapi jika Anda mengoptimalkan konsumsi browser web, mengapa tidak mendefinisikan perakitan web sebagai SSA, yang menurut saya lebih sesuai dengan apa yang saya harapkan, dan lebih sedikit upaya untuk 'mengubah' ke SSA?

Anda dapat mulai mengurai dan mengompilasi saat mengunduh, dan beberapa VM mungkin tidak melakukan kompilasi penuh di awal (misalnya, mereka mungkin hanya menggunakan garis dasar sederhana). Jadi waktu pengunduhan dan kompilasi bisa lebih kecil dari yang diharapkan, dan akibatnya penguraian dan validasi dapat menjadi faktor signifikan dalam penundaan total yang dilihat pengguna.

Mengenai representasi SSA, mereka cenderung memiliki ukuran kode yang besar. SSA sangat bagus untuk mengoptimalkan kode, tetapi tidak untuk membuat serialisasi kode secara ringkas.

@oridb Lihat komentar dari @titzer 'Desain konstruksi aliran kontrol WebAssembly menyederhanakan konsumen dengan mengaktifkan verifikasi yang cepat dan sederhana , mudah, konversi sekali jalan ke formulir SSA ...' - ini dapat menghasilkan _verifikasi_ SSA dalam satu lintasan. Bahkan jika wasm menggunakan SSA untuk pengkodean itu masih akan memiliki beban untuk memverifikasi itu, menghitung struktur dominator yang mudah dengan pembatasan aliran kontrol wasm.

Sebagian besar efisiensi pengkodean wasm tampaknya berasal dari pengoptimalan untuk pola kode umum di mana definisi memiliki penggunaan tunggal yang digunakan dalam urutan tumpukan. Saya berharap pengkodean SSA dapat melakukannya juga, sehingga dapat memiliki efisiensi penyandian yang serupa. Operator seperti if_else untuk pola berlian juga banyak membantu. Tetapi tanpa struktur wasm sepertinya semua blok dasar perlu membaca definisi dari register dan menulis hasil ke register, dan itu mungkin tidak begitu efisien. Sebagai contoh, saya pikir wasm dapat melakukan lebih baik lagi dengan operator pick yang dapat mereferensikan nilai tumpukan cakupan ke atas tumpukan dan melintasi batas blok dasar.

Saya pikir wasm tidak terlalu jauh untuk dapat menyandikan sebagian besar kode dalam gaya SSA. Jika definisi dilewatkan ke pohon lingkup sebagai output blok dasar maka itu mungkin lengkap. Mungkin pengkodean SSA ortogonal dengan masalah CFG. Misalnya Mungkin ada penyandian SSA dengan pembatasan CFG wasm, mungkin ada VM berbasis register dengan pembatasan CFG.

Tujuan wasm adalah untuk memindahkan beban pengoptimalan dari konsumen runtime. Ada resistensi yang kuat untuk menambahkan kompleksitas dalam compiler runtime, karena meningkatkan permukaan serangan. Sebagian besar tantangan desain adalah menanyakan apa yang dapat dilakukan untuk menyederhanakan kompiler runtime tanpa merusak kinerja, dan banyak perdebatan!

Yah, mungkin sudah terlambat sekarang, tetapi saya ingin mempertanyakan gagasan bahwa algoritma relooper, atau variannya, dapat menghasilkan hasil yang "cukup baik" dalam semua kasus. Mereka jelas dapat dalam banyak kasus, karena sebagian besar kode sumber tidak mengandung aliran kontrol yang tidak dapat direduksi untuk memulai, optimasi biasanya tidak membuat hal-hal terlalu berbulu, dan jika mereka melakukannya, misalnya sebagai bagian dari penggabungan blok duplikat, mereka mungkin dapat diajarkan tidak. Tapi bagaimana dengan kasus patologis? Misalnya, bagaimana jika Anda memiliki coroutine yang telah diubah oleh kompiler menjadi fungsi biasa dengan struktur seperti pseudo-C ini:

void transformed_coroutine(struct autogenerated_context_struct *ctx) {
    int arg1, arg2; // function args
    int var1, var2, var3, …; // all vars used by the function
    switch (ctx->current_label) { // restore state
    case 0:
        // initial state, load function args caller supplied and proceed to start
        arg1 = ctx->arg1;
        arg2 = ctx->arg2;
        break;
    case 1: 
        // restore all vars which are live at label 1, then jump there
        var2 = ctx->var2; 
        var3 = ctx->var3;
        goto resume_1;
    [more cases…]
    }

    [main body goes here...]
    [somewhere deep in nested control flow:]
        // originally a yield/await/etc.
        ctx->var2 = var2;
        ctx->var3 = var3;
        ctx->current_label = 1;
        return;
        resume_1:
        // continue on
}

Jadi Anda memiliki sebagian besar aliran kontrol normal, tetapi dengan beberapa gotos menunjuk di tengahnya. Ini kira-kira bagaimana coroutine LLVM bekerja .

Saya tidak berpikir ada cara yang bagus untuk mengulang sesuatu seperti itu, jika aliran kontrol 'normal' cukup kompleks. (Bisa jadi salah.) Entah Anda menduplikasi sebagian besar fungsi, berpotensi membutuhkan salinan terpisah untuk setiap titik hasil, atau Anda mengubah semuanya menjadi sakelar raksasa, yang menurut @kripken 4x lebih lambat daripada relooper pada kode biasa ( yang itu sendiri mungkin agak lebih lambat daripada tidak membutuhkan relooper sama sekali).

VM dapat mengurangi overhead sakelar raksasa dengan optimasi jump threading, tetapi tentunya lebih mahal bagi VM untuk melakukan optimasi tersebut, pada dasarnya menebak bagaimana kode direduksi menjadi gotos, daripada hanya menerima gotos eksplisit. Seperti yang dikatakan @kripken , itu juga kurang dapat diprediksi.

Mungkin melakukan transformasi semacam itu adalah ide yang buruk untuk memulai, karena setelah itu tidak ada yang mendominasi apa pun sehingga pengoptimalan berbasis SSA tidak dapat berbuat banyak… mungkin lebih baik dilakukan di tingkat perakitan, mungkin wasm pada akhirnya harus mendapatkan dukungan coroutine asli sebagai gantinya? Tetapi kompiler dapat melakukan sebagian besar pengoptimalan sebelum melakukan transformasi, dan tampaknya setidaknya perancang coroutine LLVM tidak melihat kebutuhan mendesak untuk menunda transformasi hingga pembuatan kode. Di sisi lain, karena ada cukup banyak variasi dalam semantik yang diinginkan orang dari coroutine (misalnya duplikasi coroutine yang ditangguhkan, kemampuan untuk memeriksa 'stack frames' untuk GC), ketika datang untuk merancang bytecode portabel (bukan compiler), lebih fleksibel untuk mendukung kode yang sudah diubah dengan benar daripada meminta VM melakukan transformasi.

Bagaimanapun, coroutine hanyalah salah satu contoh. Contoh lain yang dapat saya pikirkan adalah mengimplementasikan VM-dalam-a-VM. Sementara fitur yang lebih umum dari JIT adalah keluar samping , yang tidak memerlukan goto, ada situasi yang membutuhkan entri samping - lagi, membutuhkan goto ke tengah loop dan semacamnya. Penerjemah lain akan dioptimalkan: bukan berarti penafsir yang menargetkan wasm benar-benar dapat menandingi mereka yang menargetkan kode asli, yang minimal dapat meningkatkan kinerja dengan gotos yang dihitung, dan dapat masuk ke dalam perakitan untuk lebih… tetapi bagian dari motivasi untuk gotos yang dihitung adalah untuk lebih memanfaatkan prediktor cabang dengan memberikan setiap kasus instruksi lompatannya sendiri, jadi Anda mungkin dapat mereplikasi beberapa efek dengan memiliki sakelar terpisah setelah setiap penangan opcode, di mana semua kasing hanya akan menjadi gotos. Atau setidaknya memiliki satu atau dua untuk memeriksa instruksi spesifik yang biasanya datang setelah yang sekarang. Ada beberapa kasus khusus dari pola itu yang mungkin dapat direpresentasikan dengan aliran kontrol terstruktur, tetapi bukan kasus umum. Dan seterusnya…

Tentunya ada beberapa cara untuk memungkinkan aliran kontrol arbitrer tanpa membuat VM melakukan banyak pekerjaan. Gagasan manusia jerami, mungkin rusak: Anda dapat memiliki skema di mana lompatan ke cakupan anak diizinkan, tetapi hanya jika jumlah cakupan yang harus Anda masukkan kurang dari batas yang ditentukan oleh blok target. Batasnya akan default ke 0 (tidak ada lompatan dari cakupan induk), yang mempertahankan semantik saat ini, dan batas blok tidak boleh lebih besar dari batas blok induk + 1 (mudah diperiksa). Dan VM akan mengubah heuristik dominasinya dari "X mendominasi Y jika itu adalah orang tua dari Y" menjadi "X mendominasi Y jika itu adalah orang tua dari Y dengan jarak lebih besar dari batas lompatan anak Y". (Ini adalah perkiraan konservatif, tidak dijamin untuk mewakili set dominator yang tepat, tetapi hal yang sama berlaku untuk heuristik yang ada - blok dalam mungkin mendominasi bagian bawah dari blok luar.) Karena hanya kode dengan aliran kontrol yang tidak dapat direduksi perlu menentukan batas, itu tidak akan menambah ukuran kode dalam kasus umum.

Sunting: Menariknya, itu pada dasarnya akan membuat struktur blok menjadi representasi dari pohon dominasi. Saya kira akan lebih mudah untuk mengekspresikannya secara langsung: pohon blok dasar, di mana blok diizinkan untuk melompat ke blok saudara, leluhur, atau anak langsung, tetapi tidak ke keturunan lebih lanjut. Saya tidak yakin bagaimana cara terbaik memetakan ke struktur ruang lingkup yang ada, di mana "blok" dapat terdiri dari beberapa blok dasar dengan sub-loop di antaranya.

FWIW: Wasm memiliki desain tertentu, yang dijelaskan hanya dalam beberapa kata yang sangat signifikan "kecuali bahwa pembatasan bersarang tidak memungkinkan untuk bercabang ke tengah lingkaran dari luar lingkaran".

Jika itu hanya DAG maka validasi hanya dapat memeriksa apakah cabang-cabang itu maju, tetapi dengan loop ini akan memungkinkan percabangan ke tengah loop dari luar loop, maka desain blok bersarang.

CFG hanya bagian dari desain ini, yang lainnya adalah aliran data, dan ada tumpukan nilai dan blok juga dapat diatur untuk melepaskan tumpukan nilai yang dapat dengan sangat berguna mengomunikasikan rentang langsung ke konsumen yang menghemat pekerjaan konversi ke SSA .

Dimungkinkan untuk memperluas wasm menjadi penyandian SSA (tambahkan pick , izinkan blok untuk mengembalikan beberapa nilai, dan memiliki entri loop nilai pop), jadi menariknya kendala yang diminta untuk penguraian kode SSA yang efisien mungkin tidak diperlukan (karena itu sudah bisa dikodekan SSA)! Ini mengarah ke bahasa fungsional (yang mungkin memiliki pengkodean gaya tumpukan untuk efisiensi).

Jika ini diperluas untuk menangani CFG sewenang-wenang maka mungkin akan terlihat seperti berikut ini. Ini adalah pengkodean gaya SSA sehingga nilainya adalah konstanta. Tampaknya masih cocok dengan gaya tumpukan untuk sebagian besar, hanya saja tidak yakin dengan semua detailnya. Jadi dalam blocks cabang dapat dibuat ke blok berlabel lain di set itu, atau beberapa konvensi lain yang digunakan untuk mentransfer kontrol ke blok lain. Kode di dalam blok mungkin masih berguna untuk mereferensikan nilai-nilai pada tumpukan nilai yang lebih tinggi dari tumpukan untuk menyimpannya dengan meneruskan semuanya.

(func f1 (arg1)
  (let ((c1 10)) ; Some values up the stack.
    (blocks ((b1 (a1 a2 a3)
                   ... (br b3)
               (br b2 (+ a1 a2 a3 arg1 c1)))
             (b2 (a1)
                 ... (br b1 ...))
             (b3 ()
                 ...))
   .. regular structured wasm ..
   (br b2 ...)
   ....
   (br b3)
    ...
   ))

Tetapi apakah browser web akan menangani ini secara internal secara efisien?

Akankah seseorang dengan latar belakang mesin tumpukan mengenali pola kode dan dapat mencocokkannya dengan pengkodean tumpukan?

Ada beberapa diskusi menarik tentang loop yang tidak dapat direduksi di sini http://bboissin.appspot.com/static/upload/bboissin-thesis-2010-09-22.pdf

Saya tidak mengikuti semuanya dengan cepat, tetapi disebutkan mengubah loop yang tidak dapat direduksi menjadi loop yang dapat direduksi dengan menambahkan node entri. Untuk wasm kedengarannya seperti menambahkan input yang ditentukan ke loop yang khusus untuk pengiriman dalam loop, mirip dengan solusi saat ini tetapi dengan variabel yang ditentukan untuk ini. Disebutkan di atas bahwa ini divirtualisasi, dioptimalkan, dalam pemrosesan. Mungkin hal seperti ini bisa menjadi pilihan?

Jika ini di cakrawala, dan mengingat bahwa produsen sudah perlu menggunakan teknik serupa tetapi menggunakan variabel lokal, maka mungkinkah layak dipertimbangkan sekarang sehingga wasm yang diproduksi lebih awal memiliki potensi untuk berjalan lebih cepat pada runtime yang lebih maju? Ini mungkin juga menciptakan insentif untuk kompetisi antara runtime untuk mengeksplorasi ini.

Ini tidak akan menjadi label dan goto yang sewenang-wenang tetapi sesuatu yang dapat diubah menjadi ini yang memiliki beberapa peluang untuk dikompilasi secara efisien di masa depan.

Sebagai catatan, saya sangat mendukung @oridb dan @comex dalam masalah ini.
Saya pikir ini adalah masalah kritis yang harus ditangani sebelum terlambat.

Mengingat sifat WebAssembly, kesalahan apa pun yang Anda buat sekarang kemungkinan akan bertahan selama beberapa dekade yang akan datang (lihat Javascript!). Itulah mengapa masalah ini sangat kritis; hindari mendukung gotos sekarang untuk alasan apa pun itu (misalnya untuk memudahkan pengoptimalan, yang --- sejujurnya --- pengaruh implementasi tertentu atas hal yang umum, dan sejujurnya, saya pikir itu malas), dan Anda akan berakhir dengan masalah dalam jangka panjang.

Saya sudah dapat melihat implementasi WebAssembly di masa depan (atau saat ini, tetapi di masa depan) mencoba mengenali kasus khusus pola while/switch yang biasa untuk mengimplementasikan label untuk menanganinya dengan benar. Itu adalah peretasan.

WebAssembly bersih, jadi sekaranglah saatnya untuk menghindari peretasan kotor (atau lebih tepatnya, persyaratan untuk mereka).

@darkuranium :

WebAssembly seperti yang ditentukan saat ini sudah dikirimkan di browser dan rantai alat, dan pengembang telah membuat kode yang mengambil formulir yang ditata dalam desain itu. Oleh karena itu, kami tidak dapat mengubah desain secara sembarangan.

Namun, kami dapat menambahkan desain dengan cara yang kompatibel ke belakang. Saya tidak berpikir salah satu dari mereka yang terlibat berpikir goto tidak berguna. Saya menduga kita semua secara teratur menggunakan goto , dan tidak hanya dalam tata krama mainan sintaksis.

Pada titik ini, seseorang yang memiliki motivasi perlu membuat proposal yang masuk akal dan mengimplementasikannya. Saya tidak melihat proposal seperti itu ditolak jika memberikan data yang solid.

Mengingat sifat WebAssembly, kesalahan apa pun yang Anda buat sekarang kemungkinan akan bertahan selama beberapa dekade yang akan datang (lihat Javascript!). Itulah mengapa masalah ini sangat kritis; hindari mendukung gotos sekarang untuk alasan apa pun itu (misalnya untuk memudahkan pengoptimalan, yang --- sejujurnya --- pengaruh implementasi tertentu atas hal yang umum, dan sejujurnya, saya pikir itu malas), dan Anda akan berakhir dengan masalah dalam jangka panjang.

Jadi saya akan menyebut gertakan Anda: Saya pikir memiliki motivasi yang Anda tunjukkan, dan tidak menghasilkan proposal dan implementasi seperti yang saya jelaskan di atas, terus terang malas.

Aku sedang nakal tentu saja. Pertimbangkan bahwa kami memiliki orang-orang yang menggedor pintu kami untuk utas, GC, SIMD, dll—semuanya membuat argumen yang bersemangat dan masuk akal tentang mengapa fitur mereka paling penting—akan sangat bagus jika Anda dapat membantu kami mengatasi salah satu masalah ini. Ada orang yang melakukannya untuk fitur lain yang saya sebutkan. Tidak ada untuk goto sejauh ini. Silakan kenali diri Anda dengan pedoman kontribusi grup ini dan bergabunglah dengan kesenangan.

Kalau tidak, saya pikir goto adalah fitur masa depan yang hebat. Secara pribadi saya mungkin akan menangani orang lain terlebih dahulu, seperti pembuatan kode JIT. Itu minat pribadi saya setelah GC dan utas.

Hai. Saya sedang menulis terjemahan dari webassembly ke IR dan kembali ke webassembly, dan saya telah berdiskusi tentang subjek ini dengan orang-orang.

Saya telah ditunjukkan bahwa aliran kontrol yang tidak dapat direduksi sulit untuk direpresentasikan dalam perakitan web. Ini dapat terbukti merepotkan untuk mengoptimalkan kompiler yang kadang-kadang menulis aliran kontrol yang tidak dapat direduksi. Ini mungkin seperti loop di bawah, yang memiliki beberapa titik masuk:

if (x) goto inside_loop;
// banana
while(y) {
    // things
    inside_loop:
    // do things
}

Kompiler EBB akan menghasilkan yang berikut:

entry:
    cjump x, inside_loop
    // banana
    jump loop

loop:
    cjump y, exit
    // things
    jump inside_loop

inside_loop:
    // do things
    jump loop
exit:
    return

Selanjutnya kita menerjemahkan ini ke webassembly. Masalahnya adalah bahwa meskipun kami telah menemukan decompiler berabad-abad yang lalu , mereka selalu memiliki opsi untuk menambahkan goto ke dalam aliran yang tidak dapat direduksi.

Sebelum diterjemahkan, kompiler akan melakukan trik untuk ini. Tapi akhirnya Anda bisa memindai kode dan memposisikan awal dan akhir struktur. Anda berakhir dengan kandidat berikut setelah Anda menghilangkan lompatan jatuh:

<inside_loop, if(x)>
    // banana
<loop °>
<exit if(y)>
    // things
</inside_loop, if(x)>
    // do things
</loop ↑>
</exit>

Selanjutnya Anda perlu membangun tumpukan dari ini. Yang mana yang menuju ke bawah? Entah itu 'loop dalam' atau kemudian 'loop'. Kami tidak dapat melakukan ini sehingga kami harus memotong tumpukan dan menyalin hal-hal di sekitar:

if
    // do things
else
    // banana
end
loop
  br out
    // things
    // do things
end

Sekarang kita bisa menerjemahkan ini ke webassembly. Maaf, saya belum terbiasa dengan bagaimana loop ini terbentuk.

Ini bukan masalah khusus jika kita memikirkan perangkat lunak lama. Kemungkinan perangkat lunak baru diterjemahkan ke perakitan web. Tetapi masalahnya adalah bagaimana kompiler kami bekerja. Mereka telah melakukan aliran kontrol dengan blok dasar selama _dekade_ dan menganggap semuanya berjalan.

Secara teknis bahasa tersebut diterjemahkan ke dalam, kemudian diterjemahkan ke luar. Kami hanya membutuhkan mekanisme yang memungkinkan nilai-nilai mengalir melintasi batas dengan rapi tanpa drama. Alur terstruktur hanya berguna bagi orang yang ingin membaca kode.

Tetapi misalnya, berikut ini akan berfungsi dengan baik:

    cjump x, label(1)
    // banana
0: label
    cjump y, label(2)
    // things
1: label
    // do things
    jump label(0)
2: label
    // exit as usual, picking the values from the top of the stack.

Angka-angka akan tersirat, yaitu.. ketika kompiler melihat 'label', ia tahu bahwa itu memulai blok tambahan baru dan memberinya nomor indeks baru, mulai bertambah dari 0.

Untuk menghasilkan tumpukan statis, Anda dapat melacak berapa banyak item yang ada di tumpukan saat Anda menemukan lompatan ke label. Jika ada tumpukan yang tidak konsisten setelah melompat ke label, program tidak valid.

Jika Anda menemukan yang di atas buruk, Anda juga dapat mencoba menambahkan panjang tumpukan eksplisit ke setiap label (mungkin delta dari ukuran tumpukan label yang diindeks terakhir, jika nilai absolut buruk untuk kompresi), dan penanda untuk setiap lompatan tentang berapa banyak nilai itu menyalin dari atas tumpukan selama lompatan.

Saya berani bertaruh bahwa Anda tidak dapat mengakali gzip dengan cara apa pun dengan fakta bagaimana Anda mewakili aliran kontrol, sehingga Anda dapat memilih aliran yang bagus untuk orang-orang yang memiliki pekerjaan paling sulit di sini. (Saya dapat mengilustrasikan dengan rantai alat kompiler fleksibel saya untuk 'mengatasi gzip' - jika Anda suka, cukup kirimkan saya pesan dan mari kita buat demo!)

Aku merasa seperti orang yang hancur sekarang. Hanya membaca kembali spesifikasi WebAssembly dan mengambil bahwa aliran kontrol yang tidak dapat direduksi sengaja ditinggalkan dari MVP, mungkin karena alasan bahwa emscripten harus menyelesaikan masalah pada hari-hari awal.

Solusi tentang cara menangani aliran kontrol yang tidak dapat direduksi di WebAssembly dijelaskan dalam makalah "Emscripten: An LLVM-to-JavaScript Compiler". Relooper mengatur ulang program seperti ini:

_b_ = bool(x)
_b_ == 0 if
  // banana
end
block loop
  _b_ if
    // do things
    _b_ = 0
  else
    y br_if 2
    // things
    _b_ = 1
  end
  br 0
end end

Rasionalnya adalah bahwa aliran kontrol terstruktur membantu membaca dump kode sumber, dan saya kira itu diyakini membantu implementasi polyfill.

Orang-orang yang mengkompilasi dari webassembly mungkin akan beradaptasi untuk menangani dan memisahkan aliran kontrol yang runtuh.

Jadi:

  • Seperti yang disebutkan, WebAssembly sekarang stabil, jadi waktu telah berlalu untuk penulisan ulang total bagaimana aliran kontrol diekspresikan.

    • Di satu sisi, itu sangat disayangkan, karena tidak ada yang benar-benar menguji apakah pengkodean berbasis SSA yang lebih langsung dapat mencapai kekompakan yang sama dengan desain saat ini.

    • Namun, ketika datang ke spesifikasi goto, itu membuat pekerjaan menjadi lebih mudah! Instruksi berbasis blok sudah melampaui bikeshedding, dan bukan masalah besar untuk mengharapkan kompiler produksi yang menargetkan wasm untuk mengekspresikan aliran kontrol yang dapat direduksi menggunakan mereka - algoritmenya tidak terlalu sulit. Masalah utama adalah bahwa sebagian kecil dari aliran kontrol tidak dapat diekspresikan dengan menggunakan mereka tanpa biaya kinerja. Jika kita menyelesaikannya dengan menambahkan instruksi goto baru, kita tidak perlu terlalu khawatir tentang efisiensi pengkodean seperti yang kita lakukan dengan desain ulang total. Kode yang menggunakan goto harus tetap cukup ringkas, tentu saja, tetapi tidak harus bersaing dengan konstruksi lain untuk kekompakan; itu hanya untuk aliran kontrol yang tidak dapat direduksi dan harus jarang digunakan.

  • Reduksibilitas tidak terlalu berguna.

    • Sebagian besar backend kompiler menggunakan representasi SSA berdasarkan grafik blok dasar dan cabang di antara mereka. Struktur loop bersarang, hal yang menjamin reducibility, cukup banyak dibuang di awal.

    • Saya memeriksa implementasi WebAssembly saat ini di JavaScriptCore, V8, dan SpiderMonkey, dan semuanya tampaknya mengikuti pola ini. (V8 lebih rumit - semacam representasi "lautan simpul" daripada blok dasar - tetapi juga membuang struktur bersarang.)

    • Pengecualian : Analisis loop dapat berguna, dan ketiga implementasi tersebut menyampaikan informasi ke IR tentang blok dasar mana yang merupakan awal dari loop. (Bandingkan dengan LLVM yang, sebagai backend 'kelas berat' yang dirancang untuk kompilasi AOT, membuangnya dan menghitungnya kembali di backend. Ini lebih kuat, karena ia dapat menemukan hal-hal yang tidak terlihat seperti loop dalam kode sumber tetapi melakukannya setelah banyak pengoptimalan, tetapi lebih lambat.)

    • Analisis loop bekerja pada "loop alami", yang melarang cabang ke tengah loop yang tidak melewati header loop.

    • WebAssembly harus terus menjamin bahwa blok loop adalah loop alami.

    • Tetapi analisis loop tidak mengharuskan seluruh fungsi dapat direduksi, atau bahkan bagian dalam loop: itu hanya melarang cabang dari luar ke dalam. Representasi dasar masih berupa grafik aliran kendali arbitrer.

    • Aliran kontrol yang tidak dapat direduksi membuat lebih sulit untuk mengkompilasi WebAssembly ke JavaScript (polyfilling), karena kompiler harus menjalankan algoritma relooper itu sendiri.

    • Tetapi WebAssembly sudah membuat banyak keputusan yang menambahkan overhead runtime yang signifikan ke pendekatan kompilasi-ke-JS apa pun (termasuk dukungan akses memori yang tidak selaras dan menjebak akses di luar batas), menunjukkan bahwa itu tidak dianggap sangat penting.

    • Dibandingkan dengan itu, membuat compiler sedikit lebih kompleks bukanlah masalah besar.

    • Oleh karena itu, saya tidak berpikir ada alasan bagus untuk tidak menambahkan semacam dukungan untuk aliran kontrol yang tidak dapat direduksi.

  • Informasi utama yang diperlukan untuk membangun representasi SSA (yang, menurut desain, harus dimungkinkan dalam satu lintasan) adalah pohon dominator .

    • Saat ini backend dapat memperkirakan dominasi berdasarkan aliran kontrol terstruktur. Jika saya memahami spesifikasi dengan benar, instruksi berikut mengakhiri blok dasar:

    • block :



      • BB awal blok didominasi oleh BB sebelumnya.*


      • BB setelah end yang sesuai didominasi oleh BB yang memulai blok, tetapi tidak oleh BB sebelum end (karena akan dilewati jika ada br keluar ).



    • loop :



      • BB awal blok didominasi oleh BB sebelumnya.


      • BB setelah end didominasi oleh BB sebelum end (karena Anda tidak bisa masuk ke instruksi setelah end kecuali dengan mengeksekusi end ).



    • if :



      • Sisi if, sisi else, dan BB setelah end semuanya didominasi oleh BB sebelum if .



    • br , return , unreachable :



      • (BB segera setelah br , return , atau unreachable tidak dapat dijangkau.)



    • br_if , br_table :



      • BB sebelum br_if / br_table mendominasi BB setelahnya.



    • Khususnya, ini hanya perkiraan. Itu tidak dapat menghasilkan positif palsu (mengatakan A mendominasi B padahal sebenarnya tidak) karena itu hanya mengatakan demikian ketika tidak ada cara untuk sampai ke B tanpa melalui A, dengan konstruksi. Tetapi itu dapat menghasilkan negatif palsu (mengatakan A tidak mendominasi B ketika itu benar-benar terjadi), dan saya tidak berpikir algoritma single-pass dapat mendeteksi itu (bisa jadi salah).

    • Contoh negatif palsu:

      ```

      blokir $luar

      lingkaran

      br $luar ;; karena ini pecah tanpa syarat, diam-diam mendominasi akhir BB

      akhir

      akhir

    • Tapi tidak apa-apa, AFAIK.



      • Positif palsu akan buruk, karena misalnya jika blok dasar A dikatakan mendominasi blok dasar B, kode mesin untuk B dapat menggunakan set register di A (jika tidak ada yang menimpa register itu). Jika A tidak benar-benar mendominasi B, register mungkin memiliki nilai sampah.


      • Negatif palsu pada dasarnya adalah cabang hantu yang tidak pernah terjadi. Kompiler mengasumsikan bahwa cabang-cabang itu dapat terjadi, tetapi tidak harus demikian, sehingga kode yang dihasilkan hanya lebih konservatif daripada yang diperlukan.



    • Bagaimanapun, pikirkan tentang bagaimana instruksi goto harus bekerja dalam kaitannya dengan pohon dominator. Misalkan A mendominasi B, yang mendominasi C.

    • Kita tidak bisa melompat dari A ke C karena itu akan melewati B (melanggar asumsi dominasi). Dengan kata lain, kita tidak bisa melompat ke keturunan yang tidak langsung. (Dan di ujung produsen biner, jika mereka menghitung pohon dominator yang sebenarnya, tidak akan pernah ada lompatan seperti itu.)

    • Kita bisa dengan aman melompat dari A ke B, tetapi pergi ke turunan langsung tidak begitu berguna. Ini pada dasarnya setara dengan pernyataan if atau switch, yang sudah dapat kita lakukan (menggunakan instruksi if jika hanya ada tes biner, atau br_table jika ada beberapa).

    • Juga aman, dan lebih menarik, adalah melompat ke saudara atau saudara leluhur. Jika kita melompat ke saudara kita, kita mempertahankan jaminan bahwa orang tua kita mendominasi saudara kita, karena kita harus sudah mengeksekusi orang tua kita untuk sampai ke sini (karena itu juga mendominasi kita). Begitu pula untuk leluhur.

    • Secara umum, biner berbahaya dapat menghasilkan negatif palsu dalam dominasi dengan cara ini, tetapi seperti yang saya katakan, itu (a) sudah mungkin dan (b) dapat diterima.

  • Berdasarkan itu, inilah proposal strawman:

    • Satu instruksi tipe blok baru:
    • label resulttype N instr* end
    • Harus ada persis N instruksi anak langsung, di mana "anak langsung" berarti instruksi tipe blok ( loop , block , atau labels ) dan semuanya hingga yang sesuai end , atau satu instruksi non-blok (yang tidak boleh mempengaruhi tumpukan).
    • Daripada membuat satu label seperti instruksi tipe blok lainnya, labels membuat N+1 label: N menunjuk ke anak N, dan satu menunjuk ke akhir blok labels . Pada masing-masing anak, label indeks 0 sampai N-1 mengacu pada anak-anak, secara berurutan, dan label indeks N mengacu pada akhir.

    Dengan kata lain, jika Anda memiliki
    loop ;; outer labels 3 block ;; child 0 br X end nop ;; child 1 nop ;; child 2 end end

    Bergantung pada X, br mengacu pada:

    | X | Sasaran |
    | ---------- | ------ |
    | 0 | akhir block |
    | 1 | anak 0 (awal dari block ) |
    | 2 | anak 1 (tidak) |
    | 3 | anak 2 (tidak) |
    | 4 | akhir labels |
    | 5 | awal lingkaran luar |

    • Eksekusi dimulai pada anak pertama.

    • Jika eksekusi mencapai akhir salah satu anak, itu berlanjut ke yang berikutnya. Jika mencapai akhir anak terakhir, itu kembali ke anak pertama. (Ini untuk simetri, karena urutan anak-anak tidak dimaksudkan untuk menjadi signifikan.)

    • Bercabang ke salah satu anak akan membuka tumpukan operan hingga kedalamannya di awal labels .

    • Begitu juga percabangan ke akhir, tetapi jika resulttype tidak kosong, percabangan ke akhir akan memunculkan operan dan mendorongnya setelah melepas, mirip dengan block .

    • Dominasi: Blok dasar sebelum instruksi labels mendominasi setiap anak, serta BB setelah akhir labels . Anak-anak tidak mendominasi satu sama lain atau akhir.

    • Catatan desain:

    • N ditentukan di depan sehingga kode dapat divalidasi dalam satu pass. Akan aneh jika harus sampai ke ujung blok labels , untuk mengetahui jumlah anak, sebelum mengetahui target indeks di dalamnya.

    • Tidak yakin apakah pada akhirnya akan ada cara untuk meneruskan nilai pada tumpukan operan di antara label, tetapi dengan analogi dengan ketidakmampuan untuk meneruskan nilai ke dalam block atau loop , yang dapat tidak didukung untuk memulai dengan.

Akan sangat menyenangkan jika mungkin untuk melompat ke dalam lingkaran, bukan? IIUC, jika kasus itu diperhitungkan maka loop jahat+br_table combo tidak akan pernah diperlukan...

Sunting: oh, Anda dapat membuat loop tanpa loop dengan melompat ke atas di labels . Tidak percaya aku melewatkan itu.

@qwertie Jika loop yang diberikan bukan loop alami, kompiler penargetan wasm harus mengekspresikannya menggunakan labels alih-alih loop . Seharusnya tidak perlu menambahkan sakelar untuk mengekspresikan aliran kontrol, jika itu yang Anda maksud. (Lagi pula, paling buruk Anda hanya bisa menggunakan satu blok labels raksasa dengan label untuk setiap blok dasar dalam fungsi. Ini tidak memberi tahu kompiler tentang dominasi dan loop alami, jadi Anda mungkin kehilangan pengoptimalan. Namun labels hanya diperlukan jika pengoptimalan tersebut tidak berlaku.)

Struktur loop bersarang, hal yang menjamin reducibility, cukup banyak dibuang di awal. [...] Saya memeriksa implementasi WebAssembly saat ini di JavaScriptCore, V8, dan SpiderMonkey, dan semuanya tampaknya mengikuti pola ini.

Tidak cukup: setidaknya dalam SM, grafik IR bukanlah grafik yang sepenuhnya umum; kami mengasumsikan invarian grafik tertentu yang mengikuti dari yang dihasilkan dari sumber terstruktur (JS atau wasm) dan sering menyederhanakan dan/atau mengoptimalkan algoritme. Mendukung CFG yang sepenuhnya umum akan memerlukan audit/perubahan banyak lintasan dalam pipa untuk tidak mengasumsikan invarian ini (baik dengan menggeneralisasinya atau meremehkannya jika tidak dapat direduksi) atau duplikasi pemisahan simpul di depan untuk membuat grafik dapat direduksi. Hal ini tentu saja bisa dilakukan, tetapi tidak benar bahwa ini hanya masalah apakah itu hambatan buatan.

Juga, fakta bahwa ada banyak pilihan dan mesin yang berbeda akan melakukan hal yang berbeda menunjukkan bahwa memiliki kesepakatan produsen dengan ireduksi di depan akan menghasilkan kinerja yang agak lebih dapat diprediksi dengan adanya aliran kontrol yang tidak dapat direduksi.

Ketika kami telah membahas jalur yang kompatibel ke belakang untuk memperluas wasm dengan dukungan goto sewenang-wenang di masa lalu, satu pertanyaan besar adalah apa kasus penggunaannya di sini: apakah itu "membuat produsen lebih sederhana dengan tidak harus menjalankan algoritme tipe relooper" atau apakah itu "izinkan codegen yang lebih efisien untuk aliran kontrol yang benar-benar tidak dapat direduksi"? Jika hanya yang pertama, maka saya pikir kita mungkin ingin beberapa skema penyematan label/goto sewenang-wenang (yang kompatibel ke belakang dan juga menyusun dengan try/catch terstruktur blok di masa depan); itu hanya masalah menimbang biaya/manfaat dan masalah yang disebutkan di atas.

Tetapi untuk kasus penggunaan terakhir, satu hal yang kami amati adalah, ketika Anda sesekali melihat kasing perangkat Duff di alam liar (yang sebenarnya bukan cara yang efisien untuk membuka gulungan...), sering di mana Anda melihat ireduksibilitas muncul di mana kinerja penting adalah loop juru bahasa. Loop juru bahasa juga mendapat manfaat dari threading tidak langsung yang membutuhkan goto yang dihitung. Juga, bahkan dalam kompiler offline yang gemuk, loop juru bahasa cenderung mendapatkan alokasi register yang terburuk. Karena kinerja loop interpreter bisa sangat penting, satu pertanyaan adalah apakah yang benar-benar kita butuhkan adalah aliran kontrol primitif yang memungkinkan mesin untuk melakukan threading tidak langsung dan melakukan regalloc yang layak. (Ini adalah pertanyaan terbuka bagi saya.)

@lukewagner
Saya ingin mendengar lebih detail tentang pass mana yang bergantung pada invarian. Desain yang saya usulkan, menggunakan konstruksi terpisah untuk aliran yang tidak dapat direduksi, seharusnya membuatnya relatif mudah untuk lintasan pengoptimalan seperti LICM untuk menghindari aliran itu. Tetapi jika ada jenis kerusakan lain yang tidak terpikirkan oleh saya, saya ingin memahami sifatnya dengan lebih baik sehingga saya bisa mendapatkan gambaran yang lebih baik tentang apakah dan bagaimana mereka dapat dihindari.

Ketika kami telah membahas jalur yang kompatibel ke belakang untuk memperluas wasm dengan dukungan goto sewenang-wenang di masa lalu, satu pertanyaan besar adalah apa kasus penggunaannya di sini: apakah itu "membuat produsen lebih sederhana dengan tidak harus menjalankan algoritme tipe relooper" atau apakah itu "izinkan codegen yang lebih efisien untuk aliran kontrol yang benar-benar tidak dapat direduksi"?

Bagi saya itu yang terakhir; proposal saya mengharapkan produsen untuk tetap menjalankan algoritme tipe relooper untuk menyelamatkan backend pekerjaan mengidentifikasi dominator dan loop alami, kembali ke labels hanya jika diperlukan. Namun, ini masih akan membuat produsen lebih sederhana. Jika aliran kontrol yang tidak dapat direduksi memiliki penalti yang besar, produsen yang ideal harus bekerja sangat keras untuk menghindarinya, menggunakan heuristik untuk menentukan apakah lebih efisien untuk menggandakan kode, jumlah duplikasi minimal yang dapat bekerja, dll. Jika satu-satunya penalti berpotensi memberikan optimasi up loop, ini tidak benar-benar diperlukan, atau setidaknya tidak lebih diperlukan daripada dengan backend kode mesin biasa (yang memiliki optimasi loop sendiri).

Saya benar-benar harus mengumpulkan lebih banyak data tentang seberapa umum aliran kontrol yang tidak dapat direduksi dalam praktiknya…

Namun, keyakinan saya adalah bahwa menghukum aliran seperti itu pada dasarnya sewenang-wenang dan tidak perlu. Dalam kebanyakan kasus, efek pada runtime program secara keseluruhan seharusnya kecil. Namun, jika hotspot termasuk aliran kontrol yang tidak dapat direduksi, akan ada hukuman berat; di masa mendatang, panduan pengoptimalan WebAssembly mungkin menyertakan ini sebagai hal yang umum, dan menjelaskan cara mengidentifikasi dan menghindarinya. Jika keyakinan saya benar, ini adalah bentuk overhead kognitif yang sama sekali tidak perlu untuk programmer. Dan bahkan ketika overhead kecil, WebAssembly sudah memiliki cukup overhead dibandingkan dengan kode asli yang harus dicari untuk menghindari tambahan.

Saya terbuka untuk persuasi bahwa keyakinan saya tidak benar.

Karena kinerja loop interpreter bisa sangat penting, satu pertanyaan adalah apakah yang benar-benar kita butuhkan adalah aliran kontrol primitif yang memungkinkan mesin untuk melakukan threading tidak langsung dan melakukan regalloc yang layak.

Kedengarannya menarik, tetapi saya pikir akan lebih baik untuk memulai dengan primitif yang lebih umum. Bagaimanapun, primitif yang disesuaikan untuk interpreter masih membutuhkan backend untuk menangani aliran kontrol yang tidak dapat direduksi; jika Anda akan menggigit peluru itu, mungkin juga mendukung kasus umum juga.

Sebagai alternatif, proposal saya mungkin sudah berfungsi sebagai primitif yang layak untuk penerjemah. Jika Anda menggabungkan labels dengan br_table , Anda mendapatkan kemampuan untuk mengarahkan tabel lompat secara langsung ke titik arbitrer dalam fungsi, yang tidak jauh berbeda dari goto yang dihitung. (Berlawanan dengan sakelar C, yang setidaknya pada awalnya mengarahkan aliran kontrol ke titik-titik di dalam blok sakelar; jika kasing semuanya gotos, kompiler harus dapat mengoptimalkan lompatan ekstra, tetapi mungkin juga menggabungkan beberapa 'redundan' alihkan pernyataan menjadi satu, merusak manfaat memiliki lompatan terpisah setelah setiap penangan instruksi.) Saya tidak yakin apa masalah dengan alokasi register, meskipun ...

@comex Saya kira seseorang dapat dengan mudah mematikan seluruh lintasan pengoptimalan pada tingkat fungsi dengan adanya aliran kontrol yang tidak dapat direduksi (walaupun pembuatan SSA, regalloc, dan mungkin beberapa lainnya akan diperlukan dan karenanya memerlukan pekerjaan), tetapi saya berasumsi kita ingin benar-benar menghasilkan kode kualitas untuk fungsi dengan aliran kontrol yang tidak dapat direduksi dan yang melibatkan audit setiap algoritma yang sebelumnya mengasumsikan grafik terstruktur.

>

Struktur loop bersarang, hal yang dijamin oleh reducibility, adalah
cukup banyak dibuang di awal. [...] Saya memeriksa arus
Implementasi WebAssembly di JavaScriptCore, V8, dan SpiderMonkey, dan
mereka semua tampaknya mengikuti pola ini.

Tidak cukup: setidaknya dalam SM, grafik IR bukanlah grafik yang sepenuhnya umum; kami
asumsikan invarian grafik tertentu yang mengikuti dari yang dihasilkan dari a
sumber terstruktur (JS atau wasm) dan sering menyederhanakan dan/atau mengoptimalkan
algoritma.

Sama di V8. Ini sebenarnya salah satu keluhan utama saya dengan SSA di keduanya
literatur dan implementasi masing-masing yang hampir tidak pernah mereka definisikan
apa yang merupakan CFG "terbentuk dengan baik", tetapi cenderung secara implisit mengasumsikan berbagai
kendala tidak berdokumen, biasanya dipastikan oleh konstruksi oleh
antarmuka bahasa. Saya yakin banyak/sebagian besar optimasi di kompiler yang ada
tidak akan mampu menangani CFG yang benar-benar sewenang-wenang.

Seperti yang dikatakan @lukewagner , kasus penggunaan utama untuk kontrol yang tidak dapat direduksi mungkin adalah
"kode berulir" untuk penerjemah yang dioptimalkan. Sulit untuk mengatakan seberapa relevan itu
adalah untuk domain Wasm, dan apakah ketidakhadirannya adalah yang terbesar
kemacetan.

Setelah mendiskusikan aliran kontrol yang tidak dapat direduksi dengan sejumlah orang
meneliti IR kompiler, solusi "terbersih" mungkin adalah menambahkan
gagasan blok saling rekursif. Itu akan cocok dengan milik Wasm
struktur kendali cukup baik.

Optimalisasi loop di LLVM umumnya akan mengabaikan aliran kontrol yang tidak dapat direduksi dan tidak berusaha untuk mengoptimalkannya. Analisis loop yang menjadi dasarnya hanya akan mengenali loop alami, jadi Anda hanya perlu menyadari bahwa mungkin ada siklus CFG yang tidak dikenali sebagai loop. Tentu saja, pengoptimalan lainnya lebih bersifat lokal dan bekerja dengan baik dengan CFG yang tidak dapat direduksi.

Dari memori, dan mungkin salah, SPEC2006 memiliki satu loop tak tereduksi di 401.bzip2 dan hanya itu. Ini cukup langka dalam praktiknya.

Dentang hanya akan memancarkan satu instruksi indirectbr dalam fungsi menggunakan goto yang dihitung. Ini memiliki efek mengubah interpreter berulir menjadi loop alami dengan blok indirectbr sebagai header loop. Setelah meninggalkan LLVM IR, indirectbr tunggal diduplikasi dalam generator kode untuk merekonstruksi kusut aslinya.

Tidak ada algoritma verifikasi single-pass untuk aliran kontrol yang tidak dapat direduksi
yang saya sadari. Pilihan desain hanya untuk aliran kontrol yang dapat direduksi adalah
sangat dipengaruhi oleh kebutuhan ini.

Seperti disebutkan sebelumnya, aliran kontrol yang tidak dapat direduksi dapat dimodelkan setidaknya dua:
cara yang berbeda. Loop dengan pernyataan switch sebenarnya dapat dioptimalkan
ke dalam grafik asli yang tidak dapat direduksi dengan jump-threading lokal sederhana
optimasi (misalnya dengan melipat pola di mana penugasan konstanta
ke variabel lokal terjadi, maka cabang ke cabang bersyarat itu
segera mengaktifkan variabel lokal itu).

Jadi konstruksi kontrol yang tidak dapat direduksi sama sekali tidak diperlukan, dan itu adalah
hanya masalah transformasi backend kompiler tunggal untuk memulihkan
grafik asli yang tidak dapat direduksi dan optimalkan (untuk mesin yang penyusunnya
mendukung aliran kontrol yang tidak dapat direduksi--yang tidak dilakukan oleh keempat browser, untuk
yang terbaik dari pengetahuan saya).

Terbaik,
-Ben

Pada Kam, 20 Apr 2017 jam 05:20, Jakob Stoklund Olesen <
[email protected]> menulis:

Optimalisasi loop di LLVM umumnya akan mengabaikan aliran kontrol yang tidak dapat direduksi
dan tidak berusaha untuk mengoptimalkannya. Analisis lingkaran yang didasarkan pada kemauan
hanya mengenali loop alami, jadi Anda hanya perlu menyadari bahwa ada yang bisa
menjadi siklus CFG yang tidak dikenali sebagai loop. Tentu saja, lainnya
pengoptimalan lebih bersifat lokal dan berfungsi dengan baik dengan yang tidak dapat direduksi
CFG.

Dari memori, dan mungkin salah, SPEC2006 memiliki satu loop tak tereduksi di
401.bzip2 dan hanya itu. Ini cukup langka dalam praktiknya.

Dentang hanya akan memancarkan satu instruksi indirectbr dalam fungsi menggunakan
dihitung goto. Ini memiliki efek mengubah juru bahasa berulir menjadi
loop alami dengan blok indirectbr sebagai header loop. Setelah pergi
LLVM IR, indirectbr tunggal diduplikasi di pembuat kode
untuk merekonstruksi kusut asli.


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/WebAssembly/design/issues/796#issuecomment-295352983 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/ALnq1K99AR5YaQuNOIFIckLLSIZbmbd0ks5rxkJQgaJpZM4J3ofA
.

Saya juga dapat mengatakan lebih jauh bahwa jika konstruksi yang tidak dapat direduksi ditambahkan ke
WebAssembly, mereka tidak akan berfungsi di TurboFan (JIT pengoptimalan V8), jadi
fungsi akan berakhir ditafsirkan (sangat lambat) atau menjadi
dikompilasi oleh kompiler dasar (agak lebih lambat), karena kemungkinan besar kita tidak akan
menginvestasikan upaya dalam meningkatkan TurboFan untuk mendukung aliran kontrol yang tidak dapat direduksi.
Itu berarti fungsi dengan aliran kontrol yang tidak dapat direduksi di WebAssembly akan
mungkin berakhir dengan kinerja yang jauh lebih buruk.

Tentu saja, opsi lain adalah agar mesin WebAssembly di V8 menjalankan
relooper untuk memberi makan grafik yang dapat direduksi TurboFan, tetapi itu akan membuat kompilasi
(dan startup lebih buruk). Memutar ulang harus tetap menjadi prosedur offline di my
pendapat, jika tidak, kita akan berakhir dengan biaya mesin yang tak terhindarkan.

Terbaik,
-Ben

Pada Senin, 1 Mei 2017 pukul 12:48, Ben L. Titzer [email protected] menulis:

Tidak ada algoritma verifikasi single-pass untuk kontrol yang tidak dapat direduksi
aliran yang saya sadari. Pilihan desain hanya untuk aliran kontrol yang dapat direduksi
sangat dipengaruhi oleh kebutuhan ini.

Seperti disebutkan sebelumnya, aliran kontrol yang tidak dapat direduksi dapat dimodelkan setidaknya dua:
cara yang berbeda. Loop dengan pernyataan switch sebenarnya dapat dioptimalkan
ke dalam grafik asli yang tidak dapat direduksi dengan jump-threading lokal sederhana
optimasi (misalnya dengan melipat pola di mana penugasan konstanta
ke variabel lokal terjadi, maka cabang ke cabang bersyarat itu
segera mengaktifkan variabel lokal itu).

Jadi konstruksi kontrol yang tidak dapat direduksi sama sekali tidak diperlukan, dan itu adalah
hanya masalah transformasi backend kompiler tunggal untuk memulihkan
grafik asli yang tidak dapat direduksi dan optimalkan (untuk mesin yang penyusunnya
mendukung aliran kontrol yang tidak dapat direduksi--yang tidak dilakukan oleh keempat browser, untuk
yang terbaik dari pengetahuan saya).

Terbaik,
-Ben

Pada Kam, 20 Apr 2017 jam 05:20, Jakob Stoklund Olesen <
[email protected]> menulis:

Optimalisasi loop di LLVM umumnya akan mengabaikan aliran kontrol yang tidak dapat direduksi
dan tidak berusaha untuk mengoptimalkannya. Analisis lingkaran yang didasarkan pada kemauan
hanya mengenali loop alami, jadi Anda hanya perlu menyadari bahwa ada yang bisa
menjadi siklus CFG yang tidak dikenali sebagai loop. Tentu saja, lainnya
pengoptimalan lebih bersifat lokal dan berfungsi dengan baik dengan yang tidak dapat direduksi
CFG.

Dari memori, dan mungkin salah, SPEC2006 memiliki satu loop yang tidak dapat direduksi
di 401.bzip2 dan hanya itu. Ini cukup langka dalam praktiknya.

Dentang hanya akan memancarkan satu instruksi indirectbr dalam fungsi menggunakan
dihitung goto. Ini memiliki efek mengubah juru bahasa berulir menjadi
loop alami dengan blok indirectbr sebagai header loop. Setelah pergi
LLVM IR, indirectbr tunggal diduplikasi di pembuat kode
untuk merekonstruksi kusut asli.


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/WebAssembly/design/issues/796#issuecomment-295352983 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/ALnq1K99AR5YaQuNOIFIckLLSIZbmbd0ks5rxkJQgaJpZM4J3ofA
.

Ada metode yang ditetapkan untuk verifikasi linier-waktu dari aliran kontrol tak tereduksi. Contoh penting adalah JVM: dengan stackmaps, ia memiliki verifikasi waktu-linear. WebAssembly sudah memiliki tanda tangan blok pada setiap konstruksi seperti blok. Dengan informasi tipe eksplisit di setiap titik di mana beberapa jalur aliran kontrol bergabung, tidak perlu menggunakan algoritme titik tetap.

(Selain itu, beberapa waktu yang lalu saya bertanya mengapa seseorang melarang operator hipotetis pick membaca di luar bloknya pada kedalaman yang sewenang-wenang. Ini adalah satu jawaban: kecuali jika tanda tangan diperluas untuk menggambarkan semuanya pick mungkin membaca, mengetik-memeriksa pick akan memerlukan informasi lebih lanjut.)

Pola loop-with-a-switch tentu saja dapat dihilangkan, tetapi tidak praktis untuk diandalkan. Jika mesin tidak mengoptimalkannya, itu akan memiliki tingkat overhead yang mengganggu. Jika sebagian besar mesin mengoptimalkannya, maka tidak jelas apa yang dicapai dengan menjaga aliran kontrol yang tidak dapat direduksi keluar dari bahasa itu sendiri.

Huh… Tadinya aku ingin membalas, tapi hidup menghalangi.

Saya telah mempelajari beberapa mesin JS dan saya kira saya harus melemahkan klaim saya tentang aliran kontrol yang tidak dapat direduksi 'hanya berfungsi'. Saya masih tidak berpikir itu akan sulit untuk membuatnya bekerja, tetapi ada beberapa konstruksi yang akan sulit untuk beradaptasi dengan cara yang benar-benar akan menguntungkan…

Yah, mari kita asumsikan, demi argumen, bahwa membuat pipa optimasi mendukung aliran kontrol yang tidak dapat direduksi dengan benar terlalu sulit. Mesin JS masih dapat dengan mudah mendukungnya dengan cara hacky, seperti ini:

Di dalam backend, perlakukan blok labels seolah-olah itu adalah loop+switch hingga menit terakhir. Dengan kata lain, ketika Anda melihat blok labels , Anda memperlakukannya sebagai header loop dengan tepi luar yang menunjuk ke setiap label, dan ketika Anda melihat branch yang menargetkan label, Anda membuat tepi menunjuk ke labels header , bukan label target yang sebenarnya - yang harus disimpan secara terpisah di suatu tempat. Tidak perlu membuat variabel aktual untuk menyimpan label target, seperti yang harus dilakukan oleh loop+switch nyata; itu harus cukup untuk menyimpan nilai di beberapa bidang instruksi cabang, atau membuat instruksi kontrol terpisah untuk tujuan tersebut. Kemudian, optimasi, penjadwalan, bahkan alokasi register semuanya bisa berpura-pura ada dua lompatan. Tetapi ketika saatnya tiba untuk benar-benar menghasilkan instruksi lompatan asli, Anda memeriksa bidang itu, dan menghasilkan lompatan langsung ke label target.

Mungkin ada masalah dengan, misalnya, pengoptimalan apa pun yang menggabungkan/menghapus cabang, tetapi seharusnya cukup mudah untuk menghindarinya; detailnya tergantung pada desain mesin.

Dalam beberapa hal, saran saya setara dengan "optimasi jump-threading lokal sederhana" @ titzer. Saya menyarankan membuat aliran kontrol 'asli' yang tidak dapat direduksi terlihat seperti loop+switch, tetapi alternatifnya adalah mengidentifikasi loop+switch nyata - yaitu, "pola @ titzer di mana penugasan konstanta ke variabel lokal terjadi, lalu cabang ke cabang bersyarat yang segera mengaktifkan variabel lokal itu” – dan menambahkan metadata yang memungkinkan cabang tidak langsung dihapus di akhir pipeline. Jika pengoptimalan ini ada di mana-mana, ini bisa menjadi pengganti yang layak untuk instruksi eksplisit.

Either way, kelemahan yang jelas dari pendekatan hacky adalah bahwa pengoptimalan tidak memahami grafik aliran kontrol yang sebenarnya; mereka secara efektif bertindak seolah-olah label apa pun dapat melompat ke label lain. Secara khusus, alokasi register harus memperlakukan variabel sebagai variabel yang aktif di semua label, bahkan jika, katakanlah, variabel itu selalu ditetapkan tepat sebelum melompat ke label tertentu, seperti dalam kodesemu ini:

a:
  control = 1;
  goto x;
b:
  control = 2;
  goto x;
...
x:
  // use control

Itu dapat menyebabkan penggunaan register yang sangat suboptimal dalam beberapa kasus. Tetapi seperti yang akan saya perhatikan nanti, algoritme keaktifan yang digunakan JIT mungkin pada dasarnya tidak dapat melakukan ini dengan baik…

Apapun masalahnya, mengoptimalkan terlambat jauh lebih baik daripada tidak mengoptimalkan sama sekali. Satu lompatan langsung jauh lebih baik daripada lompatan + bandingkan + beban + lompatan tidak langsung; prediktor cabang CPU pada akhirnya dapat memprediksi target yang terakhir berdasarkan keadaan sebelumnya, tetapi tidak sebaik yang dapat dilakukan oleh kompiler. Dan Anda dapat menghindari pengeluaran register dan/atau memori pada variabel 'status saat ini'.

Adapun representasi, mana yang lebih baik: eksplisit ( instruksi labels atau serupa) atau implisit (optimasi loop+switch nyata mengikuti pola tertentu)?

Manfaat implisit:

  • Menjaga spesifikasi tetap ramping.

  • Mungkin sudah bekerja dengan kode loop+switch yang ada. Tapi saya belum melihat hal-hal yang dihasilkan binaryen untuk melihat apakah itu mengikuti pola yang cukup ketat.

  • Membuat cara yang diberkati untuk mengekspresikan aliran kontrol yang tidak dapat direduksi terasa seperti peretasan menyoroti fakta bahwa itu lebih lambat secara umum dan harus dihindari jika memungkinkan.

Kelemahan implisit:

  • Rasanya seperti diretas. Benar, seperti yang dikatakan @titzer , itu tidak benar-benar merugikan mesin yang 'dengan benar' mendukung aliran kontrol yang tidak dapat direduksi; mereka dapat mengenali pola lebih awal dan memulihkan aliran asli yang tidak dapat direduksi sebelum melakukan pengoptimalan. Tetap saja, tampaknya lebih rapi untuk membiarkan lompatan nyata.

  • Membuat "tebing pengoptimalan", yang biasanya dihindari oleh WebAssembly dibandingkan dengan JS. Untuk menceritakan, pola dasar yang akan dioptimalkan adalah "di mana penugasan konstanta ke variabel lokal terjadi, kemudian cabang ke cabang bersyarat yang segera mengaktifkan variabel lokal itu". Tetapi bagaimana jika, katakanlah, ada beberapa instruksi lain di antaranya, atau penugasan sebenarnya tidak menggunakan instruksi wasm const tetapi hanya sesuatu yang dikenal sebagai konstan karena pengoptimalan? Beberapa mesin mungkin lebih liberal daripada yang lain dalam apa yang mereka kenali sebagai pola ini, tetapi kemudian kode yang memanfaatkannya (sengaja atau tidak) akan memiliki kinerja yang sangat berbeda antar browser. Memiliki penyandian yang lebih eksplisit menetapkan harapan dengan lebih jelas.

  • Mempersulit penggunaan wasm seperti IR dalam langkah-langkah hipotetis pascapemrosesan. Jika kompiler penargetan wasm melakukan hal-hal dengan cara biasa dan menangani semua optimasi/transformasi dengan IR internal sebelum akhirnya menjalankan relooper dan akhirnya menghasilkan wasm, maka itu tidak akan keberatan dengan keberadaan urutan instruksi ajaib. Tetapi jika sebuah program ingin menjalankan transformasi apa pun pada kode wasm itu sendiri, itu harus menghindari memecah urutan itu, yang akan mengganggu.

Bagaimanapun, saya tidak terlalu peduli - selama, jika kita memutuskan pendekatan implisit, browser utama benar-benar berkomitmen untuk melakukan pengoptimalan yang relevan.

Kembali ke pertanyaan tentang mendukung aliran yang tidak dapat direduksi secara asli - apa kendalanya, seberapa besar manfaatnya - berikut adalah beberapa contoh spesifik dari IonMonkey tentang pass pengoptimalan yang harus dimodifikasi untuk mendukungnya:

AliasAnalysis.cpp: mengulangi blok dalam urutan terbalik (sekali), dan menghasilkan dependensi pemesanan untuk sebuah instruksi (seperti yang digunakan dalam InstructionReordering) dengan hanya melihat toko yang terlihat sebelumnya sebagai kemungkinan aliasing. Ini tidak bekerja untuk aliran kontrol siklik. Tetapi loop (ditandai secara eksplisit) ditangani secara khusus, dengan lintasan kedua yang memeriksa instruksi dalam loop terhadap penyimpanan selanjutnya di mana saja dalam loop yang sama.

-> Jadi harus ada tanda lingkaran untuk blok labels . Dalam hal ini, saya pikir menandai seluruh blok labels sebagai loop akan 'berfungsi' (tanpa menandai label individual secara khusus), karena analisisnya terlalu tidak tepat untuk peduli dengan aliran kontrol di dalam loop.

FlowAliasAnalysis.cpp: algoritma alternatif yang sedikit lebih pintar. Juga mengulangi blok dalam urutan pos terbalik, tetapi saat menghadapi setiap blok, ia menggabungkan informasi penyimpanan terakhir yang dihitung untuk masing-masing pendahulunya (diasumsikan telah dihitung), kecuali untuk header loop, di mana ia memperhitungkan backedge.

-> Messier karena mengasumsikan (a) pendahulu untuk blok dasar individu selalu muncul sebelum kecuali untuk backedge loop, dan (b) loop hanya dapat memiliki satu backedge. Ada beberapa cara berbeda untuk memperbaikinya, tetapi mungkin memerlukan penanganan eksplisit labels , dan agar algoritme tetap linier, mungkin harus bekerja cukup kasar dalam kasus itu, lebih seperti AliasAnalysis - mengurangi manfaat dibandingkan dengan pendekatan hacky. Tidak yakin bagaimana kompiler kelas berat menangani jenis pengoptimalan ini.

BacktrackingAllocator.cpp: perilaku serupa untuk alokasi register: ia melakukan reverse linier melewati daftar instruksi dan mengasumsikan bahwa semua penggunaan instruksi akan muncul setelah (yaitu diproses sebelum) definisinya, kecuali ketika menghadapi loop backedges: register yang live di awal loop tetap live di seluruh loop.

-> Setiap label perlu diperlakukan seperti header loop, tetapi liveness harus diperluas untuk seluruh blok label. Tidak sulit untuk diterapkan, tetapi sekali lagi, hasilnya tidak akan lebih baik daripada pendekatan hacky. Kupikir.

@comex Pertimbangan lain di sini adalah berapa banyak mesin wasm yang diharapkan untuk dilakukan. Misalnya, Anda menyebutkan Analisis Alias ​​Ion di atas, namun sisi lain dari cerita adalah bahwa analisis alias tidak begitu penting untuk kode WebAssembly, setidaknya untuk saat ini sementara sebagian besar program menggunakan memori linier.

Algoritme keaktifan BacktrackingAllocator.cpp Ion akan membutuhkan beberapa pekerjaan, tetapi itu tidak akan menjadi penghalang. Sebagian besar Ion sudah menangani berbagai bentuk aliran kontrol yang tidak dapat direduksi, karena OSR dapat membuat banyak entri ke dalam loop.

Pertanyaan yang lebih luas di sini adalah pengoptimalan apa yang diharapkan dilakukan oleh mesin WebAssembly. Jika seseorang mengharapkan WebAssembly menjadi platform seperti perakitan, dengan kinerja yang dapat diprediksi di mana produsen/perpustakaan melakukan sebagian besar pengoptimalan, maka aliran kontrol yang tidak dapat direduksi akan menjadi biaya yang cukup rendah karena mesin tidak memerlukan algoritme rumit yang besar di mana itu merupakan beban yang signifikan. . Jika seseorang mengharapkan WebAssembly menjadi bytecode tingkat tinggi, yang melakukan optimasi tingkat tinggi secara otomatis, dan mesin lebih kompleks, maka menjadi lebih berharga untuk menjaga aliran kontrol yang tidak dapat direduksi keluar dari bahasa, untuk menghindari kerumitan ekstra.

BTW, juga layak disebutkan dalam masalah ini adalah algoritma konstruksi SSA on-the-fly Braun et al , yang merupakan

Saya tertarik menggunakan WebAssembly sebagai qemu backend di iOS, di mana WebKit (dan tautan dinamis, tetapi yang memeriksa penandatanganan kode) adalah satu-satunya program yang diizinkan untuk menandai memori sebagai yang dapat dieksekusi. Codegen Qemu mengasumsikan bahwa pernyataan goto akan menjadi bagian dari prosesor apa pun yang harus dikodekan, yang membuat backend WebAssembly hampir mustahil tanpa menambahkan gotos.

@tbodt - Apakah Anda dapat menggunakan relooper Binaryen? Itu memungkinkan Anda menghasilkan apa yang pada dasarnya adalah Wasm-with-goto dan kemudian mengubahnya menjadi aliran kontrol terstruktur untuk Wasm.

@eholk Kedengarannya akan jauh lebih lambat daripada terjemahan langsung kode mesin ke wasm.

@tbodt Menggunakan Binaryen memang menambahkan IR ekstra di jalan, ya, tetapi seharusnya tidak lebih lambat, saya pikir, ini dioptimalkan untuk kecepatan kompilasi. Dan itu mungkin juga memiliki manfaat selain menangani gotos dll. karena Anda dapat menjalankan pengoptimal Binaryen secara opsional, yang dapat melakukan hal-hal yang tidak dilakukan oleh pengoptimal qemu (hal-hal khusus wasm).

Sebenarnya saya akan sangat tertarik untuk berkolaborasi dengan Anda dalam hal itu, jika Anda mau :) Saya pikir porting Qemu ke wasm akan sangat berguna.

Jadi setelah dipikir-pikir, gotos tidak akan banyak membantu. Codegen Qemu menghasilkan kode untuk blok dasar saat pertama kali dijalankan. Jika sebuah blok melompat ke blok yang belum dibuat, blok tersebut akan menghasilkan dan menambal blok sebelumnya dengan goto ke blok berikutnya. Pemuatan kode dinamis dan patching fungsi yang ada bukanlah hal yang bisa dilakukan di webassembly, sejauh yang saya tahu.

@kripken Saya tertarik untuk berkolaborasi, di mana tempat terbaik untuk mengobrol dengan Anda?

Anda tidak dapat menambal fungsi yang ada secara langsung, tetapi Anda dapat menggunakan call_indirect dan WebAssembly.Table untuk kode jit. Untuk setiap blok dasar yang belum dibuat, Anda dapat memanggil JavaScript, membuat modul WebAssembly dan membuat instance secara sinkron, mengekstrak fungsi yang diekspor dan menuliskannya di atas indeks dalam tabel. Panggilan selanjutnya akan menggunakan fungsi yang Anda buat.

Saya tidak yakin ada yang pernah mencoba ini, jadi kemungkinan ada banyak sisi kasar.

Itu bisa berhasil jika tailcall diterapkan. Kalau tidak, tumpukan akan meluap dengan cepat.

Tantangan lain adalah mengalokasikan ruang di tabel default. Bagaimana Anda memetakan alamat ke indeks tabel?

Pilihan lainnya adalah membuat ulang fungsi wasm pada setiap blok dasar baru. Ini berarti jumlah kompilasi ulang sama dengan jumlah blok yang digunakan, tapi saya yakin itu satu-satunya cara agar kode berjalan dengan cepat setelah dikompilasi (terutama loop dalam), dan itu tidak perlu penuh kompilasi ulang, kita dapat menggunakan kembali IR Binaryen untuk setiap blok yang ada, menambahkan IR untuk blok baru, dan menjalankan relooper pada semuanya.

(Tapi mungkin kita bisa meminta qemu untuk mengkompilasi seluruh fungsi di depan alih-alih malas?)

@tbodt untuk kolaborasi melakukan ini dengan Binaryen, satu opsi adalah membuat repo dengan pekerjaan Anda (dan dapat menggunakan masalah di sana, dll.), Yang lain adalah membuka masalah khusus di Binaryen untuk qemu.

Kami tidak dapat meminta qemu untuk mengkompilasi seluruh fungsi sekaligus, karena qemu tidak memiliki konsep "fungsi".

Adapun mengkompilasi ulang seluruh cache blok, kedengarannya mungkin memakan waktu lama. Saya akan mencari cara menggunakan profiler bawaan qemu dan kemudian membuka masalah di binaryen.

Pertanyaan sampingan. Dalam pandangan saya, bahasa yang menargetkan WebAssembly harus dapat menyediakan fungsi rekursif timbal balik yang efisien. Untuk gambaran kegunaannya, saya mengundang Anda untuk membaca: http://sharp-gamedev.blogspot.com/2011/08/forgotten-control-flow-construct.html

Secara khusus, kebutuhan yang diungkapkan oleh Cheery tampaknya ditangani oleh fungsi rekursif yang saling menguntungkan.

Saya mengerti perlunya rekursi ekor, tetapi saya bertanya-tanya apakah fungsi rekursif timbal balik hanya dapat diimplementasikan jika mesin yang mendasarinya menyediakan gotos atau tidak. Jika ya, maka bagi saya itu membuat argumen yang sah untuk mendukung mereka karena akan ada banyak bahasa pemrograman yang akan kesulitan menargetkan WebAssembly sebaliknya. Jika tidak maka mungkin hanya mekanisme minimum untuk mendukung fungsi rekursif bersama (bersama dengan rekursi ekor).

@davidgrenier , fungsi dalam modul Wasm semuanya saling rekursif. Bisakah Anda menguraikan apa yang Anda anggap tidak efisien tentang mereka? Apakah Anda hanya mengacu pada kurangnya panggilan ekor atau sesuatu yang lain?

Panggilan ekor umum akan datang. Rekursi ekor (bersama atau sebaliknya) akan menjadi kasus khusus itu.

Saya tidak mengatakan ada yang tidak efisien tentang mereka. Saya katakan, bahwa jika Anda memilikinya, Anda tidak perlu goto umum karena fungsi saling rekursif menyediakan semua yang dibutuhkan oleh pelaksana bahasa yang menargetkan WebAssembly.

Goto sangat berguna untuk pembuatan kode dari diagram dalam pemrograman visual. Mungkin sekarang pemrograman visual tidak terlalu populer tetapi di masa depan bisa mendapatkan lebih banyak orang dan saya pikir wasm harus siap untuk itu. Lebih lanjut tentang pembuatan kode dari diagram dan goto: http://drakon-editor.sourceforge.net/generation.html

Rilis Go 1.11 mendatang akan memiliki dukungan eksperimental untuk WebAssembly. Ini akan mencakup dukungan penuh untuk semua fitur Go, termasuk goroutine, saluran, dll. Namun, kinerja WebAssembly yang dihasilkan saat ini tidak begitu bagus.

Ini terutama karena instruksi goto yang hilang. Tanpa instruksi goto, kami harus menggunakan loop tingkat atas dan tabel lompat di setiap fungsi. Menggunakan algoritme relooper bukanlah pilihan bagi kami, karena ketika berpindah antar goroutine, kami harus dapat melanjutkan eksekusi pada titik yang berbeda dari suatu fungsi. Relooper tidak dapat membantu dengan ini, hanya instruksi goto yang bisa.

Sungguh luar biasa bahwa WebAssembly sampai pada titik di mana ia dapat mendukung bahasa seperti Go. Tetapi untuk benar-benar menjadi perakitan web, WebAssembly harus sama kuatnya dengan bahasa rakitan lainnya. Go memiliki kompiler canggih yang mampu memancarkan perakitan yang sangat efisien untuk sejumlah platform lain. Inilah sebabnya mengapa saya ingin berargumen bahwa ini terutama merupakan batasan WebAssembly dan bukan kompiler Go sehingga tidak mungkin juga menggunakan kompiler ini untuk memancarkan Majelis yang efisien untuk web.

Menggunakan algoritme relooper bukanlah pilihan bagi kami, karena ketika berpindah antar goroutine, kami harus dapat melanjutkan eksekusi pada titik yang berbeda dari suatu fungsi.

Hanya untuk memperjelas, goto biasa tidak akan cukup untuk itu, goto yang dihitung diperlukan untuk kasus penggunaan Anda, apakah itu benar?

Saya pikir goto biasa mungkin cukup dalam hal kinerja. Lompatan di antara blok dasar tetap statis dan untuk mengganti goroutine br_table dengan gotos di cabangnya harus cukup berkinerja. Ukuran output adalah pertanyaan yang berbeda.

Sepertinya Anda memiliki aliran kontrol normal di setiap fungsi, tetapi juga membutuhkan kemampuan untuk melompat dari entri fungsi ke lokasi tertentu lainnya di "tengah", saat melanjutkan goroutine - ada berapa banyak lokasi seperti itu? Jika setiap blok dasar, maka relooper akan dipaksa untuk memancarkan loop tingkat atas yang dilalui setiap instruksi, tetapi jika hanya beberapa, itu seharusnya tidak menjadi masalah. (Itulah sebenarnya yang terjadi dengan dukungan setjmp di emscripten - kami hanya membuat jalur tambahan yang diperlukan antara blok dasar LLVM, dan membiarkan proses relooper itu normal.)

Setiap panggilan ke beberapa fungsi lain adalah lokasi seperti itu dan sebagian besar blok dasar memiliki setidaknya satu instruksi panggilan. Kami kurang lebih melepas dan memulihkan tumpukan panggilan.

Saya mengerti, terima kasih. Ya, saya setuju bahwa agar praktis, Anda memerlukan goto statis atau dukungan pemulihan tumpukan panggilan (yang juga telah dipertimbangkan).

Apakah mungkin untuk memanggil fungsi dalam gaya CPS atau mengimplementasikan call/cc di WASM?

@Heimdell , dukungan untuk beberapa bentuk kelanjutan yang dibatasi (alias "stack switching") ada di peta jalan, yang seharusnya cukup untuk hampir semua abstraksi kontrol yang menarik. Kami tidak dapat mendukung kelanjutan yang tidak terbatas (yaitu, panggilan/cc penuh), karena tumpukan panggilan Wasm dapat secara sewenang-wenang bercampur dengan bahasa lain, termasuk panggilan masuk kembali ke penyemat, dan dengan demikian tidak dapat dianggap dapat disalin atau dipindahkan.

Membaca utas ini, saya mendapat kesan bahwa label dan gotos yang sewenang-wenang memiliki rintangan besar sebelum menjadi fitur:

  • Aliran kontrol tidak terstruktur memungkinkan grafik aliran kontrol yang tidak dapat direduksi
  • Menghilangkan* "verifikasi cepat, sederhana, mudah, konversi sekali jalan ke formulir SSA"
  • Membuka kompiler JIT ke kinerja nonlinier
  • Orang yang menjelajahi halaman web tidak perlu mengalami penundaan jika kompiler bahasa asli dapat melakukan pekerjaan di muka

_*walaupun mungkin ada alternatif seperti algoritma konstruksi SSA on-the-fly Braun et al yang menangani aliran kontrol tak tereduksi_

Jika kita masih terjebak di sana, panggilan _dan_ ekor bergerak maju, mungkin ada baiknya meminta kompiler bahasa untuk tetap menerjemahkan ke gotos, tetapi sebagai langkah terakhir sebelum keluaran WebAssembly, pisahkan "blok label" menjadi fungsi, dan ubah goto menjadi panggilan ekor.

Menurut makalah desainer Skema Guy Steele tahun 1977, Lambda: The Ultimate GOTO , transformasi harus dimungkinkan, dan kinerja panggilan ekor harus dapat menyamai gotos.

Pikiran?

Jika kita masih terjebak di sana, panggilan _dan_ ekor bergerak maju, mungkin ada baiknya meminta kompiler bahasa untuk tetap menerjemahkan ke gotos, tetapi sebagai langkah terakhir sebelum keluaran WebAssembly, pisahkan "blok label" menjadi fungsi, dan ubah goto menjadi panggilan ekor.

Ini pada dasarnya adalah apa yang akan dilakukan oleh setiap kompiler, tidak ada seorang pun yang saya tahu yang menganjurkan goto yang tidak dikelola dari jenis yang menyebabkan begitu banyak masalah di JVM, hanya untuk grafik EBB yang diketik. LLVM, GCC, Cranelift, dan yang lainnya memiliki CFG bentuk SSA (mungkin tidak dapat direduksi) sebagai representasi internalnya dan kompiler dari Wasm ke native memiliki representasi internal yang sama, jadi kami ingin menyimpan sebanyak mungkin informasi itu dan merekonstruksi informasi itu sesedikit mungkin. Lokal lossy, karena mereka tidak lagi SSA, dan aliran kontrol Wasm lossy, karena itu bukan lagi CFG yang sewenang-wenang. AFAIK menjadikan Wasm sebagai mesin register SSA register tak terbatas dengan informasi liveness register berbutir halus yang tertanam mungkin akan menjadi yang terbaik untuk codegen tetapi ukuran kode akan membengkak, mesin stack dengan aliran kontrol yang dimodelkan pada CFG sewenang-wenang mungkin adalah jalan tengah terbaik . Saya mungkin salah tentang ukuran kode dengan mesin register, mungkin untuk menyandikannya secara efisien.

Masalahnya tentang aliran kontrol yang tidak dapat direduksi adalah bahwa jika itu tidak dapat direduksi di front-end itu masih tidak dapat direduksi dalam wasm, konversi relooper/stackifier tidak membuat aliran kontrol dapat direduksi, itu hanya mengubah irreducibility menjadi bergantung pada nilai runtime. Ini memberikan informasi yang lebih sedikit kepada backend sehingga dapat menghasilkan kode yang lebih buruk, satu-satunya cara untuk menghasilkan kode yang baik untuk CFG yang tidak dapat direduksi saat ini adalah dengan mendeteksi pola yang dipancarkan oleh relooper dan stackifier dan mengubahnya kembali menjadi CFG yang tidak dapat direduksi. Kecuali Anda mengembangkan V8, yang AFAIK hanya mendukung aliran kontrol yang dapat direduksi, mendukung aliran kontrol yang tidak dapat direduksi adalah murni kemenangan - itu membuat frontend dan backend menjadi lebih sederhana (frontend hanya dapat memancarkan kode dalam format yang sama dengan yang mereka simpan secara internal, backend tidak 't harus mendeteksi pola) sambil menghasilkan output yang lebih baik dalam hal aliran kontrol tidak dapat direduksi dan output yang sama baiknya atau lebih baik dalam kasus biasa bahwa aliran kontrol dapat direduksi.

Plus itu akan memungkinkan GCC dan Go untuk mulai memproduksi WebAssembly.

Saya tahu V8 adalah komponen penting dari ekosistem WebAssembly tetapi tampaknya menjadi satu-satunya bagian dari ekosistem itu yang mendapat manfaat dari situasi aliran kontrol saat ini, semua backend lain yang saya ketahui akan dikonversi ke CFG dan tidak terpengaruh oleh apakah WebAssembly dapat mewakili aliran kontrol yang tidak dapat direduksi atau tidak.

Tidak bisakah v8 menggabungkan relooper untuk menerima CFG input? Sepertinya sebagian besar ekosistem diblokir pada detail implementasi v8.

Hanya untuk referensi, saya perhatikan pernyataan switch di c++ sangat lambat di wasm. Ketika saya memprofilkan kode, saya harus mengonversinya ke bentuk lain yang beroperasi jauh lebih cepat untuk melakukan pemrosesan gambar. Dan itu tidak pernah menjadi masalah pada arsitektur lain. Saya benar-benar ingin goto karena alasan kinerja.

@graph , dapatkah Anda memberikan detail lebih lanjut tentang bagaimana "pernyataan peralihan lambat"? Selalu mencari peluang untuk meningkatkan kinerja... (Jika Anda tidak ingin menghentikan utas ini, kirim email langsung ke saya, [email protected].)

Saya akan memposting di sini karena ini berlaku untuk semua browser. Pernyataan sederhana seperti ini ketika dikompilasi dengan emscripten di mana lebih cepat ketika saya mengonversi ke pernyataan if.

for(y = ....) {
    for(x = ....) {
        switch(type){
        case IS_RGBA:....
         ....
        case IS_BGRA
        ....
        case IS_RGB
        ....
....

Saya berasumsi kompiler sedang mengonversi tabel lompatan ke apa pun yang didukung wasm. Saya tidak melihat perakitan yang dihasilkan sehingga tidak dapat mengonfirmasi.

Saya tahu beberapa hal yang tidak terkait dengan hal-hal kecil yang dapat dioptimalkan untuk pemrosesan gambar di web. Saya sudah mengirimkannya melalui tombol "umpan balik" di firefox. Jika Anda tertarik, beri tahu saya dan saya akan mengirimi Anda email masalahnya.

@graph Patokan lengkap akan sangat membantu di sini. Secara umum, sakelar di C dapat berubah menjadi tabel lompatan yang sangat cepat di wasm, tetapi ada beberapa kasus sudut yang belum berfungsi dengan baik, yang mungkin perlu kita perbaiki, baik di LLVM atau di browser.

Khususnya di emscripten, cara penanganan sakelar banyak berubah antara backend fastcomp lama dan upstream baru, jadi jika Anda melihat ini beberapa waktu lalu, atau baru-baru ini tetapi menggunakan fastcomp, alangkah baiknya untuk memeriksa upstream.

@graph , Jika emscripten menghasilkan br_table maka jit terkadang akan menghasilkan tabel lompatan dan terkadang (jika dianggap akan lebih cepat) ia akan mencari ruang kunci secara linier atau dengan pencarian biner in-line. Apa yang dilakukannya seringkali tergantung pada ukuran sakelar. Tentu saja mungkin kebijakan pemilihannya tidak optimal... Saya setuju dengan @kripken , kode runnable akan sangat membantu di sini jika Anda memiliki beberapa untuk dibagikan.

(Tidak tahu tentang v8 atau jsc, tetapi Firefox saat ini tidak mengenali rantai if-then-else sebagai sakelar yang memungkinkan, jadi biasanya bukan ide yang baik untuk membuka sakelar kode selama rantai if-then-else. titik impas mungkin tidak lebih dari dua atau tiga perbandingan.)

@lars-t-hansen @kripken @graph mungkin br_table saat ini sangat tidak dioptimalkan karena pertukaran ini tampaknya menunjukkan: https://twitter.com/battagline/status/116831096515883008

@aardappel , itu aneh, benchmark yang saya jalankan kemarin tidak menunjukkan ini, di firefox di sistem saya titik impasnya sekitar 5 kasus seperti yang saya ingat dan setelah itu br_table adalah pemenangnya. microbenchmark tentu saja, dan dengan beberapa upaya pemerataan kunci pencarian. jika sarang "jika" bias terhadap kunci yang paling mungkin sehingga tidak lebih dari beberapa tes diperlukan maka sarang "jika" akan menang.

Jika tidak dapat melakukan analisis rentang pada nilai sakelar untuk menghindarinya, maka br_table juga harus melakukan setidaknya satu tes penyaringan untuk rentang sakelar, yang juga memakan keuntungannya.

@lars-t-hansen Ya, kami tidak tahu test case-nya, mungkin ada nilai outliernya. Either way, sepertinya Chrome memiliki lebih banyak pekerjaan yang harus dilakukan daripada Firefox.

Saya sedang berlibur, karenanya saya kurang mendapat balasan. Terima kasih atas pengertian.

@kripken @lars-t-hansen Saya telah menjalankan beberapa tes sepertinya ya itu lebih baik sekarang di firefox. Masih ada beberapa kasus di mana if-else out-performs switch. Berikut ini adalah kasus:


Main.cpp

#include <stdio.h>

#include <chrono>
#include <random>

class Chronometer {
public:
    Chronometer() {

    }

    void start() {
        mStart = std::chrono::steady_clock::now();
    }

    double seconds() {
        std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
        return std::chrono::duration_cast<std::chrono::duration<double>>(end - mStart).count();
    }

private:
    std::chrono::steady_clock::time_point mStart;
};

int main() {
    printf("Starting tests!\n");
    Chronometer timer;
    // we want to prevent optimizations based on known size as most applications
    // do not know the size in advance.
    std::random_device rd;  //Will be used to obtain a seed for the random number engine
    std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()
    std::uniform_int_distribution<> dis(100000000, 1000000000);
    std::uniform_int_distribution<> opKind(0, 3);
    int maxFrames = dis(gen);
    int switchSelect = 0;
    constexpr int SW1 = 1;
    constexpr int SW2 = 8;
    constexpr int SW3 = 32;
    constexpr int SW4 = 38;

    switch(opKind(gen)) {
    case 0:
        switchSelect = SW1;
        break;
    case 1:
        switchSelect = SW2; break;
    case 2:
        switchSelect = SW3; break;
    case 4:
        switchSelect = SW4; break;
    }
    printf("timing with SW = %d\n", switchSelect);
    timer.start();
    int accumulator = 0;
    for(int i = 0; i < maxFrames; ++i) {
        switch(switchSelect) {
        case SW1:
            accumulator = accumulator*3 + i; break;
        case SW2:
            accumulator = (accumulator < 3)*i; break;
        case SW3:
            accumulator = (accumulator&0xFF)*i + accumulator; break;
        case SW4:
            accumulator = (accumulator*accumulator) - accumulator + i; break;
        }
    }
    printf("switch time = %lf seconds\n", timer.seconds());
    printf("accumulated value: %d\n", accumulator);
    timer.start();
    accumulator = 0;
    for(int i = 0; i < maxFrames; ++i) {
        if(switchSelect == SW1)
            accumulator = accumulator*3 + i;
        else if(switchSelect == SW2)
            accumulator = (accumulator < 3)*i;
        else if(switchSelect == SW3)
            accumulator = (accumulator&0xFF)*i + accumulator;
        else if(switchSelect == SW4)
            accumulator = (accumulator*accumulator) - accumulator + i;
    }
    printf("if-else time = %lf seconds\n", timer.seconds());
    printf("accumulated value: %d\n", accumulator);

    return 0;
}

Tergantung pada nilai switchSelect. if-else mengungguli. Contoh keluaran:

Starting tests!
timing with SW = 32
switch time = 2.049000 seconds
accumulated value: 0
if-else time = 0.401000 seconds
accumulated value: 0

Seperti yang Anda lihat untuk switchSelect = 32 if-else jauh lebih cepat. Untuk kasus lain if-else sedikit lebih cepat. Untuk kasus switchSelect = 1 & 0, pernyataan switch lebih cepat.

Test in Firefox 69.0.3 (64-bit)
compiled using: emcc -O3 -std=c++17 main.cpp -o main.html
emcc version: emcc (Emscripten gcc/clang-like replacement) 1.39.0 (commit e047fe4c1ecfae6ba471ca43f2f630b79516706b)

Menggunakan emscripen stabil terbaru pada 20 Oktober 2019. Instal baru ./emcc activate latest .

Saya perhatikan di atas ada kesalahan ketik, tetapi seharusnya tidak mempengaruhi masalah bahwa if-else adalah kasus SW3 yang lebih cepat karena mereka menjalankan instruksi yang sama.

lagi dengan ini melampaui titik impas 5: Menarik bahwa untuk switchSelect=32 untuk kasus ini kecepatannya serupa dengan if-else. Seperti yang Anda lihat untuk 1003 if-else sedikit lebih cepat. Switch harus menang dalam kasus ini.

Starting tests!
timing with SW = 1003
switch time = 2.253000 seconds
accumulated value: 1903939380
if-else time = 2.197000 seconds
accumulated value: 1903939380


main.cpp

#include <stdio.h>

#include <chrono>
#include <random>

class Chronometer {
public:
    Chronometer() {

    }

    void start() {
        mStart = std::chrono::steady_clock::now();
    }

    double seconds() {
        std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
        return std::chrono::duration_cast<std::chrono::duration<double>>(end - mStart).count();
    }

private:
    std::chrono::steady_clock::time_point mStart;
};

int main() {
    printf("Starting tests!\n");
    Chronometer timer;
    // we want to prevent optimizations based on known size as most applications
    // do not know the size in advance.
    std::random_device rd;  //Will be used to obtain a seed for the random number engine
    std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()
    std::uniform_int_distribution<> dis(100000000, 1000000000);
    std::uniform_int_distribution<> opKind(0, 8);
    int maxFrames = dis(gen);
    int switchSelect = 0;
    constexpr int SW1 = 1;
    constexpr int SW2 = 8;
    constexpr int SW3 = 32;
    constexpr int SW4 = 38;
    constexpr int SW5 = 64;
    constexpr int SW6 = 67;
    constexpr int SW7 = 1003;
    constexpr int SW8 = 256;

    switch(opKind(gen)) {
    case 0:
        switchSelect = SW1;
        break;
    case 1:
        switchSelect = SW2; break;
    case 2:
        switchSelect = SW3; break;
    case 3:
        switchSelect = SW4; break;
    case 4:
        switchSelect = SW5; break;
    case 5:
        switchSelect = SW6; break;
    case 6:
        switchSelect = SW7; break;
    case 7:
        switchSelect = SW8; break;
    }
    printf("timing with SW = %d\n", switchSelect);
    timer.start();
    int accumulator = 0;
    for(int i = 0; i < maxFrames; ++i) {
        switch(switchSelect) {
        case SW1:
            accumulator = accumulator*3 + i; break;
        case SW2:
            accumulator = (accumulator < 3)*i; break;
        case SW3:
            accumulator = (accumulator&0xFF)*i + accumulator; break;
        case SW4:
            accumulator = (accumulator*accumulator) - accumulator + i; break;
        case SW5:
            accumulator = (accumulator << 3) - accumulator + i; break;
        case SW6:
            accumulator = (i - accumulator) & 0xFF; break;
        case SW7:
            accumulator = i*i + accumulator; break;
        }
    }
    printf("switch time = %lf seconds\n", timer.seconds());
    printf("accumulated value: %d\n", accumulator);
    timer.start();
    accumulator = 0;
    for(int i = 0; i < maxFrames; ++i) {
        if(switchSelect == SW1)
            accumulator = accumulator*3 + i;
        else if(switchSelect == SW2)
            accumulator = (accumulator < 3)*i;
        else if(switchSelect == SW3)
            accumulator = (accumulator&0xFF)*i + accumulator;
        else if(switchSelect == SW4)
            accumulator = (accumulator*accumulator) - accumulator + i;
        else if(switchSelect == SW5)
            accumulator = (accumulator << 3) - accumulator + i;
        else if(switchSelect == SW6)
            accumulator = (i - accumulator) & 0xFF;
        else if(switchSelect == SW7)
            accumulator = i*i + accumulator;

    }
    printf("if-else time = %lf seconds\n", timer.seconds());
    printf("accumulated value: %d\n", accumulator);

    return 0;
}


Terima kasih teman-teman untuk melihat ke dalam kasus uji ini.

Itu adalah switch sangat jarang, yang harus dikonversi oleh LLVM menjadi setara dengan satu set if-then, tetapi tampaknya ia melakukannya dengan cara yang kurang efisien daripada manual if-thens. Sudahkah Anda mencoba menjalankan wasm2wat untuk melihat bagaimana kedua loop ini berbeda dalam kode?

Hal ini juga sangat bergantung pada pengujian ini dengan menggunakan nilai yang sama pada setiap iterasi. Tes ini akan lebih baik jika didaur ulang melalui semua nilai, atau lebih baik lagi, dipilih secara acak dari mereka (jika itu bisa dilakukan dengan murah).

Lebih baik lagi, alasan sebenarnya orang menggunakan sakelar untuk kinerja adalah dengan rentang yang padat, sehingga Anda dapat menjamin itu benar-benar menggunakan br_table bawahnya. Melihat berapa banyak kasus br_table lebih cepat dari if akan menjadi hal yang paling berguna untuk diketahui.

Sakelar dalam loop ketat digunakan karena ini adalah kode yang lebih bersih daripada kinerja. Tetapi untuk wasm, dampak kinerjanya terlalu besar sehingga diubah menjadi pernyataan if yang lebih buruk. Untuk pemrosesan gambar di banyak kasus penggunaan saya jika saya ingin lebih banyak kinerja dari sebuah sakelar, saya akan memindahkan sakelar di luar loop, dan hanya memiliki salinan loop untuk setiap kasus. Biasanya sakelar hanya beralih di antara beberapa bentuk format piksel, format warna, pengkodean, dan lain-lain ... Dan dalam banyak kasus konstanta dihitung melalui definisi atau enum dan tidak linier. Saya melihat sekarang bahwa masalah saya tidak terkait dengan desain goto. Saya baru saja memiliki pemahaman yang tidak lengkap tentang apa yang terjadi untuk pernyataan peralihan saya. Saya harap catatan saya berguna untuk pengembang browser yang membaca ini untuk mengoptimalkan wasm untuk pemrosesan gambar dalam kasus ini. Terima kasih.

Saya tidak pernah berpikir goto bisa menjadi perdebatan yang begitu panas . Saya dalam perahu setiap bahasa harus memiliki goto 😁 . Alasan lain untuk menambahkan goto adalah karena mengurangi kerumitan kompiler untuk dikompilasi ke wasm. Saya cukup yakin itu disebutkan di atas di suatu tempat. Sekarang saya tidak punya apa-apa untuk dikeluhkan .

Ada kemajuan lebih lanjut di sana?

karena perdebatan sengit saya akan menganggap beberapa browser akan menambahkan dukungan untuk goto sebagai ekstensi bytecode non-standar. Maka mungkin GCC bisa masuk ke game sebagai pendukung versi non-standar. Yang menurut saya tidak bagus secara keseluruhan tetapi akan memungkinkan lebih banyak kompetisi kompiler. Apakah ini sudah dipertimbangkan?

Belum ada banyak kemajuan akhir-akhir ini, tetapi Anda mungkin ingin melihat proposal fungsi .

@graph bagi saya, saran Anda terdengar seperti "mari kita hancurkan semuanya, dan berharap menjadi lebih baik".
Ini tidak bekerja seperti itu. Ada BANYAK manfaat dari struktur WebAssembly saat ini (sayangnya tidak jelas). Cobalah menyelam lebih dalam ke filosofi wasm.

Mengizinkan "Label dan Gotos sewenang-wenang" akan membawa kita kembali ke masa (kuno) bytecode yang tidak dapat diverifikasi. Semua kompiler hanya akan beralih ke "cara malas" dalam melakukan sesuatu, alih-alih "melakukannya dengan benar".

Jelas bahwa wasm dalam kondisinya saat ini memiliki beberapa kelalaian besar. Orang-orang bekerja untuk mengisi kesenjangan (seperti yang disebutkan oleh @binji ), tetapi saya tidak berpikir "struktur wasm global" perlu dikerjakan ulang. Hanya pendapat sederhana saya.

@vshymanskyy Proposal funclets, yang menyediakan fungsionalitas yang setara dengan label dan goto arbitrer, sepenuhnya dapat divalidasi, dalam waktu linier.

Saya juga harus menyebutkan bahwa dalam kompiler Wasm waktu linier kami, kami secara internal mengkompilasi semua aliran kontrol Wasm ke dalam representasi seperti fungsi, yang saya punya beberapa informasi tentang di posting blok ini dan konversi dari aliran kontrol Wasm ke representasi internal ini diimplementasikan di sini . Kompiler mendapatkan semua informasi tipenya dari representasi seperti fungsi ini, jadi cukuplah untuk mengatakan bahwa memvalidasi keamanan tipenya dalam waktu linier adalah hal yang sepele.

Saya pikir kesalahpahaman ini bahwa aliran kontrol yang tidak dapat direduksi tidak dapat divalidasi dalam waktu linier berasal dari JVM, di mana aliran kontrol yang tidak dapat direduksi harus dieksekusi menggunakan penerjemah alih-alih dikompilasi. Ini karena JVM tidak memiliki cara apa pun untuk mewakili jenis metadata untuk aliran kontrol yang tidak dapat direduksi, sehingga tidak dapat melakukan konversi dari mesin tumpukan ke mesin register. "Goto sewenang-wenang" (yaitu melompat ke byte/instruksi X) tidak dapat diverifikasi sama sekali, tetapi memisahkan fungsi ke dalam blok yang diketik, yang kemudian dapat dilompati dalam urutan sewenang-wenang, tidak lebih sulit untuk diverifikasi daripada memisahkan modul menjadi fungsi yang diketik , yang kemudian dapat dilompati dalam urutan arbitrer. Anda tidak perlu goto tak bertipe jump-to-byte-X-style untuk mengimplementasikan pola berguna apa pun yang akan dipancarkan oleh kompiler seperti GCC dan LLVM.

Saya hanya menyukai proses di sini. Sisi A menjelaskan mengapa ini diperlukan dalam aplikasi tertentu. Sisi B mengatakan mereka melakukan kesalahan, tetapi tidak menawarkan dukungan untuk aplikasi itu. Sisi A menjelaskan bagaimana tidak ada argumen pragmatis B yang bertahan. Sisi B tidak mau berurusan dengan itu karena mereka pikir pihak A salah melakukannya. Sisi A sedang mencoba untuk mencapai tujuan. Sisi B mengatakan itu tujuan yang salah, menyebutnya malas atau kasar. Makna filosofis yang lebih dalam hilang di sisi A. Pragmatis hilang di sisi B, karena mereka mengklaim memiliki semacam landasan moral yang lebih tinggi. Sisi A melihat ini sebagai operasi mekanistik amoral. Pada akhirnya, sisi B umumnya tetap mengendalikan spesifikasi untuk lebih baik atau lebih buruk, dan mereka telah menyelesaikan jumlah yang luar biasa dengan kemurnian relatif mereka.

Sejujurnya, saya hanya terjebak di sini karena bertahun-tahun yang lalu, saya mencoba membuat port TinyCC ke WASM sehingga saya dapat menjalankan lingkungan pengembangan pada ESP8266 yang menargetkan ESP8266. Saya hanya memiliki penyimpanan ~4MB, jadi memasukkan re-looper dan beralih ke AST, serta banyak perubahan lainnya tidak mungkin. (Catatan tambahan: Bagaimana relooper satu-satunya hal seperti relooper? Ini sangat buruk dan tidak ada yang menulis ulang pengisap itu di In C!?) Bahkan jika itu mungkin pada saat ini, saya tidak tahu apakah saya akan menulis target TinyCC ke WASM karena itu tidak menarik lagi bagi saya.

benang ini, meskipun. Astaga, utas ini telah memberi saya begitu banyak kegembiraan eksistensial. Untuk menyaksikan bifurkasi dalam kemanusiaan berjalan lebih dalam dari demokrat atau republik, atau agama. Saya merasa jika ini bisa diselesaikan. Jika A bisa datang untuk tinggal di dunia B, atau B memvalidasi klaim A bahwa pemrograman prosedural memiliki tempatnya... Saya merasa kita bisa memecahkan perdamaian dunia.

Bisakah seseorang yang bertanggung jawab atas V8 mengkonfirmasi di utas ini bahwa oposisi terhadap aliran kontrol yang tidak dapat direduksi tidak dipengaruhi oleh implementasi V8 saat ini?

Saya bertanya karena ini yang paling mengganggu saya. Bagi saya sepertinya ini harus menjadi diskusi di level spec tentang pro dan kontra dari fitur ini. Seharusnya sama sekali tidak dipengaruhi oleh bagaimana implementasi tertentu saat ini dirancang. Namun, ada pernyataan yang membuat saya percaya bahwa implementasi V8 memengaruhi ini. Mungkin saya salah. Pernyataan terbuka mungkin bisa membantu.

Yah, meskipun sangat disayangkan, implementasi saat ini yang ada sejauh ini sangat penting sehingga masa depan (mungkin lebih lama dari masa lalu) tidak begitu penting. Saya mencoba menjelaskan bahwa di #1202, bahwa konsistensi lebih penting daripada beberapa implementasi, tetapi sepertinya saya delusi. Semoga berhasil menjelaskan bahwa beberapa keputusan pengembangan di suatu tempat di beberapa proyek tidak merupakan kebenaran universal, atau harus, secara default, dianggap benar.

Utas ini adalah salah satu kenari di tambang batubara W3C. Meskipun saya sangat menghormati banyak individu W3C, keputusan untuk mempercayakan JavaScript ke Ecma International, dan bukan W3C, tidak dibuat tanpa prasangka.

Seperti @cnlohr , saya memiliki harapan untuk port wasm TCC, dan untuk alasan yang bagus;

"Wasm dirancang sebagai target kompilasi portabel untuk bahasa pemrograman, memungkinkan penerapan di web untuk aplikasi klien dan server." - webassembly.org

Tentu, siapa pun dapat memberi kepausan mengapa goto adalah [INSERT JARGON], tetapi bagaimana kalau kita lebih memilih standar daripada opini. Kita semua bisa setuju POSIX C adalah target dasar yang baik, terutama mengingat bahasa saat ini dibuat dari atau dibandingkan dengan C dan judul beranda WASM menyebut dirinya sebagai target kompilasi portabel untuk bahasa. Tentu, beberapa fitur akan dipetakan seperti utas dan simd. Tetapi, mengabaikan sepenuhnya sesuatu yang mendasar seperti goto , bahkan tidak memberikannya kesopanan pemetaan jalan, tidak konsisten dengan tujuan yang dinyatakan WASM dan sikap seperti itu dari badan standardisasi yang memberi lampu hijau <marquee> berada di luar batas.

Menurut SEI CERT C Coding Standard Rec. "Pertimbangkan untuk menggunakan rantai goto saat meninggalkan fungsi pada kesalahan saat menggunakan dan melepaskan sumber daya" ;

Banyak fungsi memerlukan alokasi banyak sumber daya. Gagal dan kembali ke suatu tempat di tengah fungsi ini tanpa membebaskan semua sumber daya yang dialokasikan dapat menghasilkan kebocoran memori. Ini adalah kesalahan umum untuk lupa membebaskan satu (atau semua) sumber daya dengan cara ini, jadi rantai goto adalah cara paling sederhana dan terbersih untuk mengatur pintu keluar sambil mempertahankan urutan sumber daya yang dibebaskan.

Rekomendasi kemudian menawarkan contoh dengan solusi POSIX C yang disukai menggunakan goto . Penentang akan menunjukkan catatan bahwa goto masih dianggap berbahaya . Menariknya, pendapat ini tidak diwujudkan dalam salah satu standar pengkodean tertentu, hanya sebuah catatan. Yang membawa kita ke kenari, yang "dianggap berbahaya".

Intinya, pertimbangan "Kawasan CSS" atau goto sebagai berbahaya seharusnya hanya dipertimbangkan bersama dengan solusi yang diusulkan untuk masalah yang digunakan fitur tersebut. Jika menghapus fitur "berbahaya" tersebut sama dengan menghapus kasus penggunaan yang wajar tanpa alternatif, itu bukan solusi, itu sebenarnya berbahaya bagi pengguna bahasa tersebut.

Fungsi tidak nol biaya, bahkan di C. Jika seseorang menawarkan pengganti gotos & label, canihaz tolong! Jika seseorang mengatakan saya tidak membutuhkannya, bagaimana mereka tahu itu? Dalam hal kinerja, goto dapat memberi kami sedikit tambahan, sulit untuk membantah para insinyur bahwa kami tidak memerlukan fitur berkinerja dan mudah digunakan yang telah ada sejak awal bahasa.

Tanpa rencana untuk mendukung goto , WASM adalah target kompilasi mainan, dan tidak apa-apa, mungkin begitulah cara W3C melihat web. Saya berharap WASM sebagai standar mencapai lebih tinggi, keluar dari ruang alamat 32bit, dan memasuki perlombaan kompilasi. Saya harap wacana rekayasa dapat menjauh dari "itu tidak mungkin..." untuk mempercepat ekstensi GCC C seperti Label sebagai Nilai karena WASM harus LUAR BIASA. Secara pribadi, TCC jauh lebih mengesankan dan lebih berguna pada saat ini, tanpa semua pujian yang sia-sia, tanpa halaman arahan hipster dan logo mengkilap.

@d4tocchini :

Menurut SEI CERT C Coding Standard Rec. "Pertimbangkan untuk menggunakan rantai goto saat meninggalkan fungsi pada kesalahan saat menggunakan dan melepaskan sumber daya" ;

Banyak fungsi memerlukan alokasi banyak sumber daya. Gagal dan kembali ke suatu tempat di tengah fungsi ini tanpa membebaskan semua sumber daya yang dialokasikan dapat menghasilkan kebocoran memori. Ini adalah kesalahan umum untuk lupa membebaskan satu (atau semua) sumber daya dengan cara ini, jadi rantai goto adalah cara paling sederhana dan terbersih untuk mengatur pintu keluar sambil mempertahankan urutan sumber daya yang dibebaskan.

Rekomendasi kemudian menawarkan contoh dengan solusi POSIX C pilihan menggunakan goto . Penentang akan menunjukkan catatan bahwa goto masih dianggap berbahaya . Menariknya, pendapat ini tidak diwujudkan dalam satu standar pengkodean tertentu, hanya sebuah catatan. Yang membawa kita ke kenari, yang "dianggap berbahaya".

Contoh yang diberikan dalam rekomendasi itu dapat langsung dinyatakan dengan jeda berlabel, yang tersedia di Wasm. Itu tidak membutuhkan kekuatan ekstra dari goto yang sewenang-wenang. (C tidak memberikan label break and continue, jadi harus mundur ke goto lebih sering dari yang diperlukan.)

@rossberg , poin bagus tentang jeda berlabel dalam contoh itu, tetapi saya tidak setuju dengan asumsi kualitatif Anda bahwa C harus "mundur". goto adalah konstruksi yang lebih kaya daripada jeda berlabel. Jika C akan disertakan di antara target kompilasi portabel, dan C tidak mendukung jeda berlabel, itu adalah titik bisu. Java telah memberi label break/continues sedangkan Python menolak fitur yang diusulkan , dan mengingat baik sun JVM dan CPython default ditulis dalam C, tidakkah Anda setuju C sebagai bahasa yang didukung harus lebih tinggi pada daftar prioritas?

Jika goto ingin segera dihapus dari pertimbangan, apakah ratusan penggunaan goto dalam sumber emscripten juga harus dipertimbangkan kembali?

Apakah ada bahasa yang tidak bisa ditulis dalam C? C sebagai bahasa harus menginformasikan fitur WASM. Jika POSIX C tidak memungkinkan dengan WASM hari ini, maka ada peta jalan Anda yang tepat.

Tidak benar-benar pada topik argumen, tetapi untuk tidak menaungi bahwa kesalahan acak mengintai di sana-sini dalam argumentasi secara umum:

Python telah memberi label istirahat

Bisakah Anda menguraikan? (Aka: Python tidak memiliki jeda berlabel.)

@pfalcon , ya saya buruk, saya mengedit komentar saya untuk mengklarifikasi python yang diusulkan berlabel istirahat/berlanjut dan menolaknya

Jika goto begitu mudah dihilangkan dari pertimbangan, haruskah ratusan penggunaan goto dalam sumber emscripten dipertimbangkan kembali juga?

1) Perhatikan berapa banyak yang ada di musl libc, tidak langsung di emscripten. (Kedua yang paling banyak digunakan adalah tes/pihak ketiga)
2) Konstruksi tingkat sumber tidak sama dengan instruksi bytecode
3) Emscripten tidak pada tingkat abstraksi yang sama dengan standar wasm, jadi, tidak, itu tidak boleh dipertimbangkan kembali atas dasar itu.

Secara khusus, mungkin berguna hari ini untuk menulis ulang goto dari libc, karena dengan begitu kita akan memiliki kontrol lebih besar atas cfg yang dihasilkan daripada memercayai relooper/cfgstackify untuk menanganinya dengan baik. Kami belum melakukannya karena jumlah pekerjaan yang tidak sepele untuk berakhir dengan kode yang sangat berbeda dari musl hulu.

Pengembang Emscripten (terakhir saya periksa) cenderung berpendapat bahwa struktur seperti goto akan sangat bagus, karena alasan yang jelas, jadi tidak mungkin untuk menjatuhkannya dari pertimbangan, bahkan jika perlu bertahun-tahun untuk mencapai kompromi yang dapat diterima.

sikap seperti itu dari badan standardisasi sehingga <marquee> yang menyala hijau tidak dapat diprediksi.

Ini adalah pernyataan yang sangat bodoh.

1) We-the-broader-Internet lebih dari satu dekade lagi untuk membuat keputusan itu
2) We-the-wasm-CG adalah kelompok orang yang sepenuhnya (hampir?) terpisah dari tag itu, dan mungkin secara individual terganggu oleh kesalahan masa lalu yang jelas juga.

tanpa semua kepausan yang sia-sia, tanpa halaman arahan hipster dan logo mengkilap.

Ini bisa saja ditulis ulang menjadi "Saya frustrasi" tanpa mengalami masalah nada.

Seperti yang ditunjukkan oleh utas ini, percakapan ini cukup sulit.

Ada tingkat baru yang sangat memprihatinkan ketika Anda ingin menulis ulang serangkaian fungsi yang sangat tepercaya dan dipahami untuk semua yang baru hanya karena lingkungan penggunaannya harus melalui langkah-langkah ekstra untuk mendukungnya. (walaupun saya masih berada di kubu tolong-tambah-goto yang tegas karena saya benci terikat hanya menggunakan satu kompiler tertentu)

Saya pikir utas ini sudah jauh dari produktif - telah berjalan selama lebih dari empat tahun sekarang dan sepertinya setiap argumen yang mungkin untuk dan melawan goto s sewenang-wenang telah digunakan di sini; juga harus dicatat bahwa tidak satu pun dari argumen itu yang sangat baru;)

Ada runtime terkelola yang memilih untuk tidak memiliki label lompatan sewenang-wenang, yang bekerja dengan baik untuk mereka. Juga, ada sistem pemrograman di mana lompatan sewenang-wenang diizinkan dan mereka juga bekerja dengan baik. Pada akhirnya, penulis sistem pemrograman membuat pilihan desain dan hanya waktu yang benar-benar menunjukkan apakah pilihan itu berhasil atau tidak.

Pilihan desain Wasm yang melarang lompatan sewenang-wenang adalah inti filosofinya. Tidak mungkin itu dapat mendukung goto s tanpa sesuatu seperti funclet, untuk alasan yang sama tidak mendukung lompatan tidak langsung murni.

Pilihan desain Wasm yang melarang lompatan sewenang-wenang adalah inti filosofinya. Tidak mungkin itu dapat mendukung gotos tanpa sesuatu seperti funclet, untuk alasan yang sama tidak mendukung lompatan tidak langsung murni.

@penzn Mengapa proposal fungsi macet? Itu ada sejak Oktober 2018 dan masih dalam fase 0.

Jika kita sedang mendiskusikan proyek open-source run-of-the-mill, saya akan membayar & selesai dengan itu. Kita berbicara tentang standar monopoli yang luas di sini. Respon masyarakat yang kuat harus dipupuk karena kita peduli.

@J0eCool

  1. Perhatikan berapa banyak yang ada di musl libc, tidak langsung di emscripten. (Kedua yang paling banyak digunakan adalah tes/pihak ketiga)

Ya, anggukannya adalah seberapa banyak digunakan di C secara umum.

  1. Konstruksi tingkat sumber tidak sama dengan instruksi bytecode

Tentu saja, apa yang kita diskusikan adalah masalah internal yang berdampak pada konstruksi tingkat sumber. Itu bagian dari frustrasi, kotak hitam seharusnya tidak membocorkan kekhawatirannya.

  1. Emscripten tidak pada tingkat abstraksi yang sama dengan standar wasm, jadi, tidak, itu tidak boleh dipertimbangkan kembali atas dasar itu.

Intinya adalah Anda akan menemukan goto s di sebagian besar proyek C yang cukup besar, bahkan di dalam rantai alat WebAssembly pada umumnya. Target kompiler portabel untuk bahasa secara umum yang tidak cukup ekspresif untuk menargetkan kompilernya sendiri tidak sepenuhnya konsisten dengan sifat perusahaan kami.

Secara khusus, mungkin berguna hari ini untuk menulis ulang goto dari libc, karena dengan begitu kita akan memiliki kontrol lebih besar atas cfg yang dihasilkan daripada memercayai relooper/cfgstackify untuk menanganinya dengan baik.

Ini melingkar. Banyak orang di atas telah mengajukan pertanyaan-pertanyaan serius yang belum terjawab mengenai tidak dapat salahnya persyaratan semacam itu.

Kami belum melakukannya karena jumlah pekerjaan yang tidak sepele untuk berakhir dengan kode yang sangat berbeda dari musl hulu.

Dimungkinkan untuk menghapus gotos, seperti yang Anda katakan, ini adalah pekerjaan yang tidak sepele ! Apakah Anda menyarankan semua orang harus menyimpang jalur kode karena gotos tidak boleh didukung?

Pengembang Emscripten (terakhir saya periksa) cenderung berpendapat bahwa struktur seperti goto akan sangat bagus, karena alasan yang jelas, jadi tidak mungkin untuk menjatuhkannya dari pertimbangan, bahkan jika perlu bertahun-tahun untuk mencapai kompromi yang dapat diterima.

Secercah harapan! Saya akan puas jika dukungan goto/label ditanggapi secara serius dengan item peta jalan + undangan resmi untuk membuat bola bergerak, bahkan jika sudah bertahun-tahun.

Ini adalah pernyataan yang sangat bodoh.

Kamu benar. Maafkan hiperbola, saya agak frustrasi. Saya suka wasm, dan sering menggunakannya, tetapi pada akhirnya saya melihat jalan yang menyakitkan di depan saya jika saya ingin melakukan sesuatu yang penting dengannya, seperti port TCC. Setelah membaca semua komentar dan artikel, saya masih tidak bisa menebak apakah oposisi itu teknis, filosofis atau politik. Seperti yang diungkapkan @neelance ,

“Bisakah seseorang yang bertanggung jawab atas V8 mengkonfirmasi di utas ini bahwa perlawanan terhadap aliran kontrol yang tidak dapat direduksi tidak dipengaruhi oleh implementasi V8 saat ini?

Saya bertanya karena ini yang paling mengganggu saya. [...]

Jika kalian mendengarkan apa pun yang berguna, ambil umpan balik @neelance tentang Go 1.11 ke dalam hati. Itu sulit untuk diperdebatkan. Tentu, kita semua dapat melakukan pembersihan goto yang tidak sepele, tetapi meskipun demikian, kita menerima pukulan perf serius yang hanya dapat diperbaiki dengan instruksi goto.

Sekali lagi, maafkan rasa frustrasi saya, tetapi jika masalah ini ditutup tanpa alamat yang tepat, maka saya khawatir itu akan mengirimkan jenis sinyal yang salah yang hanya akan memperburuk tanggapan komunitas semacam ini dan tidak sesuai untuk salah satu upaya standar terbesar kami. bidang. Tak perlu dikatakan bahwa saya adalah penggemar berat dan pendukung semua orang di tim ini. Terima kasih!

Berikut ini adalah masalah dunia nyata lain yang disebabkan oleh goto/funclets yang hilang: https://github.com/golang/go/issues/42979

Untuk program ini, kompiler Go saat ini menghasilkan biner wasm dengan 18.000 block bersarang. Biner wasm itu sendiri memiliki ukuran 2,7 MB, tetapi ketika saya menjalankannya melalui wasm2wat saya mendapatkan file .wat 4,7GB. 🤯.

Saya dapat mencoba memberi kompiler Go beberapa heuristik sehingga alih-alih satu tabel lompatan besar, ia dapat membuat semacam pohon biner dan kemudian melihat variabel target lompatan beberapa kali. Tapi apakah ini benar-benar bagaimana seharusnya dengan wasm?

Saya ingin menambahkan bahwa saya merasa aneh bagaimana orang tampaknya berpikir bahwa tidak apa-apa jika hanya satu kompiler (Emscripten[1]) yang secara realistis dapat mendukung WebAssembly.
Sedikit mengingatkan saya pada situasi libopus (standar yang secara normatif bergantung pada kode berhak cipta).

Saya juga merasa aneh bagaimana para pengembang WebAssembly tampaknya sangat menentang hal ini, meskipun hampir semua orang dari ujung kompiler mengatakan kepada mereka bahwa itu diperlukan. Ingat: WebAssembly adalah standar, bukan manifesto. Dan faktanya, sebagian besar kompiler modern menggunakan beberapa bentuk blok dasar SSA + secara internal (atau sesuatu yang hampir setara, dengan properti yang sama), yang tidak memiliki konsep loop eksplisit [2]. Bahkan JIT menggunakan sesuatu yang serupa, begitu umum.
Persyaratan mutlak untuk pengulangan terjadi tanpa jalan keluar dari "hanya gunakan goto", sepengetahuan saya[3], belum pernah terjadi sebelumnya di luar penerjemah bahasa-ke-bahasa --- dan bahkan kemudian, hanya penerjemah bahasa-ke-bahasa yang menargetkan bahasa tanpa goto. Secara khusus, saya belum pernah mendengar ini harus dilakukan untuk segala jenis IR atau bytecode, selain WebAssembly.

Mungkin sudah waktunya untuk mengganti nama WebAssembly menjadi WebEmscripten (WebScripten?).

Seperti yang dikatakan @ d4tocchini , jika bukan karena status monopolistik WebAssembly (diperlukan, karena situasi standarisasi), kemungkinan akan bercabang sekarang, menjadi sesuatu yang dapat secara wajar mendukung apa yang sudah diketahui oleh pengembang kompiler yang perlu didukung.
Dan tidak, "gunakan saja emscripten" bukan argumen tandingan yang valid, karena membuat standar bergantung pada satu vendor kompiler. Saya harap saya tidak perlu memberi tahu Anda mengapa itu buruk.

EDIT: Saya lupa menambahkan satu hal:
Anda masih belum mengklarifikasi apakah masalah ini teknis, filosofis, atau politis. Saya menduga yang terakhir, tetapi dengan senang hati akan terbukti salah (karena masalah teknis dan filosofis dapat diperbaiki jauh lebih mudah daripada politik).

Berikut adalah masalah dunia nyata lain yang disebabkan oleh goto/fungsi yang hilang: golang/go#42979

Untuk program ini, kompiler Go saat ini menghasilkan biner wasm dengan 18.000 block bersarang. Biner wasm itu sendiri memiliki ukuran 2,7 MB, tetapi ketika saya menjalankannya melalui wasm2wat saya mendapatkan file .wat 4,7GB. 🤯.

Saya dapat mencoba memberi kompiler Go beberapa heuristik sehingga alih-alih satu tabel lompatan besar, ia dapat membuat semacam pohon biner dan kemudian melihat variabel target lompatan beberapa kali. Tapi apakah ini benar-benar bagaimana seharusnya dengan wasm?

Contoh ini sangat menarik. Bagaimana program garis lurus yang sederhana menghasilkan kode ini? Apa hubungan antara jumlah elemen array dan jumlah blok? Secara khusus, haruskah saya menafsirkan ini sebagai arti bahwa setiap akses elemen array membutuhkan _multiple_ blok untuk dikompilasi dengan tepat?

Dan tidak, "gunakan saja emscripten" bukan argumen tandingan yang valid

Saya pikir argumen tandingan sebenarnya dalam nada ini adalah bahwa kompiler lain yang ingin menargetkan Wasm dapat/harus mengimplementasikan algoritma seperti relooper mereka sendiri. Secara pribadi, saya pikir Wasm pada akhirnya harus memiliki loop multi-tubuh (dekat dengan funclets) atau sesuatu yang serupa yang merupakan target alami untuk goto .

@conrad-watt Ada beberapa faktor yang menyebabkan setiap penugasan menggunakan beberapa blok dasar dalam CFG. Salah satunya adalah adanya cek panjang pada irisan karena panjangnya tidak diketahui pada waktu kompilasi. Secara umum saya akan mengatakan bahwa kompiler menganggap blok dasar sebagai konstruksi yang relatif murah, tetapi dengan wasm mereka agak mahal, terutama dalam kasus khusus ini.

@neelance dalam contoh yang dimodifikasi di mana kode dibagi antara beberapa fungsi, overhead memori (runtime/kompilasi) terbukti jauh lebih rendah. Apakah lebih sedikit blok yang dihasilkan dalam kasus ini, atau hanya karena fungsi yang terpisah membuat GC mesin bisa lebih granular?

@conrad-watt Bahkan bukan kode Go yang menggunakan memori, tetapi Host WebAssembly: Ketika saya membuat instance biner wasm dengan Chrome 86, CPU saya menjadi 100% selama 2 menit dan penggunaan memori dari tab memuncak pada 11.3GB. Ini sebelum kode biner / Go wasm dieksekusi. Ini adalah bentuk biner wasm yang menyebabkan masalah.

Itu sudah menjadi pemahaman saya. Saya berharap sejumlah besar anotasi blok/tipe menyebabkan overhead memori secara khusus selama kompilasi/instantiasi.

Untuk mencoba dan memperjelas pertanyaan saya sebelumnya - jika versi terpisah dari kode dikompilasi ke Wasm dengan lebih sedikit blok (karena beberapa kekhasan relooper), itu akan menjadi salah satu penjelasan untuk overhead memori yang berkurang, dan akan menjadi motivasi yang baik untuk menambahkan yang lebih umum mengontrol aliran ke Wasm.

Atau, mungkin kode split menghasilkan (kira-kira) jumlah total blok yang sama, tetapi karena setiap fungsi dikompilasi JIT secara terpisah, metadata/IR yang digunakan untuk mengompilasi setiap fungsi dapat lebih bersemangat di-GC oleh mesin Wasm . Masalah serupa terjadi di V8 tahun yang lalu ketika mem-parsing/mengkompilasi fungsi asm.js besar. Dalam hal ini, memperkenalkan aliran kontrol yang lebih umum ke Wasm tidak akan menyelesaikan masalah.

Pertama saya ingin mengklarifikasi: Kompiler Go tidak menggunakan algoritma relooper, karena secara inheren tidak sesuai dengan konsep switching goroutine. Semua blok dasar diekspresikan melalui tabel lompatan dengan sedikit fall-through jika memungkinkan.

Saya kira ada beberapa pertumbuhan kompleksitas eksponensial dalam runtime wasm Chrome sehubungan dengan kedalaman block s bersarang. Versi split memiliki jumlah blok yang sama tetapi kedalaman maksimum yang lebih kecil.

Dalam hal ini, memperkenalkan aliran kontrol yang lebih umum ke Wasm tidak akan menyelesaikan masalah.

Saya setuju bahwa masalah kompleksitas ini mungkin dapat diselesaikan di ujung Chrome. Tapi saya selalu suka bertanya "Mengapa masalah ini ada sejak awal?". Saya berpendapat bahwa dengan aliran kontrol yang lebih umum, masalah ini tidak akan pernah ada. Juga, masih ada overhead kinerja umum yang signifikan karena semua blok dasar dinyatakan sebagai tabel lompatan, yang menurut saya tidak mungkin hilang dengan pengoptimalan.

Saya kira ada beberapa pertumbuhan kompleksitas eksponensial di runtime wasm Chrome sehubungan dengan kedalaman blok bersarang. Versi split memiliki jumlah blok yang sama tetapi kedalaman maksimum yang lebih kecil.

Apakah ini berarti bahwa dalam fungsi garis lurus dengan N akses larik, akses larik terakhir akan bersarang (beberapa faktor konstan) dalam N blok? Jika demikian, apakah ada cara untuk menguranginya dengan memfaktorkan kode penanganan kesalahan secara berbeda? Saya mengharapkan kompiler apa pun untuk menenggak jika harus menganalisis 3000 loop bersarang (analogi yang sangat kasar) jadi jika ini tidak dapat dihindari karena alasan semantik, itu juga akan menjadi argumen untuk aliran kontrol yang lebih umum.

Jika perbedaan bersarang kurang mencolok dari itu, firasat saya adalah bahwa V8 hampir tidak melakukan GC'ing metadata _selama_ kompilasi dari fungsi Wasm tunggal, jadi bahkan jika kami memiliki sesuatu seperti proposal fungsi tweak dalam bahasa sejak awal , overhead yang sama masih akan terlihat tanpa mereka melakukan beberapa pengoptimalan GC yang menarik.

Juga, masih ada overhead kinerja umum yang signifikan karena semua blok dasar dinyatakan sebagai tabel lompatan, yang menurut saya tidak mungkin hilang dengan pengoptimalan.

Setuju bahwa jelas lebih baik (dari sudut pandang teknis murni) untuk memiliki target yang lebih alami di sini.

Apakah ini berarti bahwa dalam fungsi garis lurus dengan N akses larik, akses larik terakhir akan bersarang (beberapa faktor konstan) dalam N blok? Jika demikian, apakah ada cara untuk menguranginya dengan memfaktorkan kode penanganan kesalahan secara berbeda? Saya mengharapkan kompiler apa pun untuk menenggak jika harus menganalisis 3000 loop bersarang (analogi yang sangat kasar) jadi jika ini tidak dapat dihindari karena alasan semantik, itu juga akan menjadi argumen untuk aliran kontrol yang lebih umum.

Sebaliknya: Tugas pertama bersarang sedalam itu, bukan yang terakhir. block s bersarang dan satu br_table di atas adalah bagaimana pernyataan switch tradisional diekspresikan dalam wasm. Ini adalah tabel lompatan yang saya sebutkan. Tidak ada 3000 loop bersarang.

Jika perbedaan bersarang kurang mencolok dari itu, firasat saya adalah bahwa V8 hampir tidak melakukan GC'ing metadata selama kompilasi fungsi Wasm tunggal, jadi bahkan jika kami memiliki sesuatu seperti proposal fungsi tweak dalam bahasa sejak awal , overhead yang sama masih akan terlihat tanpa mereka melakukan beberapa pengoptimalan GC yang menarik.

Ya, mungkin juga ada beberapa implementasi yang memiliki kompleksitas eksponensial sehubungan dengan jumlah blok dasar. Tetapi menangani blok dasar (bahkan dalam jumlah besar) adalah apa yang dilakukan banyak kompiler sepanjang hari. Misalnya kompiler Go sendiri menangani jumlah blok dasar ini dengan mudah selama kompilasi, meskipun mereka diproses oleh beberapa lintasan optimasi.

Ya, mungkin juga ada beberapa implementasi yang memiliki kompleksitas eksponensial sehubungan dengan jumlah blok dasar. Tetapi menangani blok dasar (bahkan dalam jumlah besar) adalah apa yang dilakukan banyak kompiler sepanjang hari. Misalnya kompiler Go sendiri menangani jumlah blok dasar ini dengan mudah selama kompilasi, meskipun mereka diproses oleh beberapa lintasan optimasi.

Tentu, tetapi masalah kinerja di sini akan menjadi ortogonal dengan bagaimana aliran kontrol antara blok-blok dasar tersebut diekspresikan dalam bahasa sumber asli (yaitu bukan motivasi untuk aliran kontrol yang lebih umum di Wasm). Untuk melihat apakah V8 sangat buruk di sini, orang dapat memeriksa apakah FireFox/SpiderMonkey atau Lucet/Cranelift menunjukkan overhead kompilasi yang sama.

Saya telah melakukan beberapa pengujian lagi: Firefox dan Safari tidak menunjukkan masalah sama sekali. Menariknya, Chrome bahkan dapat menjalankan kode sebelum proses intensif selesai, jadi sepertinya beberapa tugas yang tidak sepenuhnya diperlukan untuk menjalankan biner wasm mengalami masalah kompleksitas.

Tentu, tetapi masalah kinerja di sini akan ortogonal dengan bagaimana aliran kontrol antara blok dasar tersebut diekspresikan dalam bahasa sumber asli.

Saya mengerti maksud Anda.

Saya masih percaya bahwa mewakili blok dasar tidak melalui instruksi lompat tetapi melalui variabel lompatan dan tabel lompatan besar / blok bersarang mengekspresikan konsep sederhana blok dasar dengan cara yang cukup kompleks. Ini mengarah pada overhead kinerja dan risiko masalah kompleksitas seperti yang kita lihat di sini. Saya percaya bahwa sistem yang lebih sederhana lebih baik dan lebih kuat daripada sistem yang kompleks. Saya masih belum melihat argumen yang meyakinkan saya bahwa sistem yang lebih sederhana adalah pilihan yang buruk. Saya hanya mendengar bahwa V8 akan kesulitan menerapkan aliran kontrol arbitrer dan pertanyaan terbuka saya untuk memberi tahu saya bahwa pernyataan ini salah (https://github.com/WebAssembly/design/issues/796#issuecomment-623431527) belum sudah dijawab belum.

@neelance

Chrome bahkan dapat menjalankan kode sebelum proses intensif selesai

Kedengarannya seperti kompiler dasar Liftoff tidak masalah, dan masalahnya ada pada kompiler pengoptimalan TurboFan. Silakan ajukan masalah, atau berikan testcase dan saya bisa mengajukannya jika Anda mau.

Lebih umum: Apakah menurut Anda rencana peralihan tumpukan wasm akan dapat menyelesaikan masalah implementasi goroutine Go? Itu tautan terbaik yang dapat saya temukan, tetapi sekarang cukup aktif, dengan pertemuan dua mingguan, dan beberapa kasus penggunaan yang kuat yang memotivasi pekerjaan. Jika Go dapat menggunakan coroutine wasm untuk menghindari pola sakelar besar maka saya pikir goto sewenang-wenang tidak diperlukan.

Compiler Go tidak menggunakan algoritma relooper, karena secara inheren tidak sesuai dengan konsep switching goroutine.

Memang benar itu tidak bisa diterapkan dengan sendirinya. Namun, kami mendapatkan hasil yang baik dengan menggunakan aliran kontrol terstruktur wasm +

Saya akan sangat senang untuk bereksperimen dengan itu di Go, jika Anda tertarik! Ini jelas tidak akan sebagus dukungan switching stack built-in di wasm, tetapi bisa lebih baik daripada pola sakelar besar. Dan akan lebih mudah untuk beralih ke dukungan pengalihan tumpukan bawaan nanti. Secara konkret, cara kerja eksperimen ini adalah membuat Go memancarkan kode yang terstruktur secara normal, tanpa khawatir tentang peralihan tumpukan sama sekali, dan hanya memancarkan panggilan ke fungsi maybe_switch_goroutine pada titik yang sesuai. Transformasi Asyncify pada dasarnya akan mengurus semua sisanya.

Saya tertarik pada gotos untuk emulator kompilasi ulang dinamis seperti qemu. Tidak seperti kompiler lain, qemu sama sekali tidak memiliki pengetahuan tentang struktur aliran kontrol program, sehingga gotos adalah satu-satunya target yang masuk akal. Tailcalls mungkin mengatasi ini, dengan mengkompilasi setiap blok sebagai fungsi dan setiap goto sebagai tailcall.

@kripken Terima kasih atas posting Anda yang sangat membantu.

Kedengarannya seperti kompiler dasar Liftoff tidak masalah, dan masalahnya ada pada kompiler pengoptimalan TurboFan. Silakan ajukan masalah, atau berikan testcase dan saya bisa mengajukannya jika Anda mau.

Berikut biner wasm yang dapat Anda jalankan dengan wasm_exec.html .

Apakah menurut Anda rencana peralihan tumpukan wasm akan dapat menyelesaikan masalah implementasi goroutine Go?

Ya, pada pandangan pertama sepertinya ini akan membantu.

Namun, kami mendapatkan hasil yang baik dengan menggunakan aliran kontrol terstruktur wasm + Asyncify.

Ini terlihat menjanjikan juga. Kita perlu mengimplementasikan relooper di Go, tapi kurasa tidak masalah. Satu kelemahan kecil adalah ia menambahkan ketergantungan ke binaryen untuk memproduksi binari wasm. Saya mungkin akan segera menulis proposal.

Saya percaya algoritma stackifier LLVM lebih mudah/lebih baik, jika Anda ingin menerapkannya: https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2

Saya telah mengajukan proposal untuk proyek Go: https://github.com/golang/go/issues/43033

@neelance, bagus untuk melihat @kripken saran 's membantu sedikit dengan golang + wasm. Mempertimbangkan masalah ini adalah salah satu goto/label bukan peralihan tumpukan, dan mengingat bahwa Asyncify memperkenalkan deps/pembuatan casing khusus baru dengan Asyncify hingga pengalihan tumpukan dirilis, dll -- apakah Anda akan menggolongkan ini sebagai solusi atau kurang dari mitigasi optimal? Bagaimana ini dibandingkan dengan perkiraan manfaat jika instruksi goto tersedia?

Jika argumen “Good Taste” Linus Torvalds untuk daftar tertaut bertumpu pada keanggunan menghilangkan satu-satunya pernyataan cabang berselubung khusus, sulit untuk melihat senam selubung khusus semacam ini sebagai kemenangan atau bahkan langkah ke arah yang benar. Setelah secara pribadi menggunakan goto untuk api seperti async di C, untuk berbicara tentang peralihan tumpukan sebelum instruksi goto memicu semua jenis bau.

Harap perbaiki saya jika saya salah membaca, tetapi terlepas dari tanggapan yang tampaknya tidak masuk akal yang berfokus pada kekhususan marjinal untuk beberapa pertanyaan yang diajukan, tampaknya pengelola di sini belum menawarkan kejelasan tentang masalah yang dihadapi atau menjawab pertanyaan sulit. Dengan segala hormat, bukankah pengerasan yang lamban ini merupakan ciri dari kalus politik korporasi? Jika ini masalahnya, saya mengerti keadaannya... Bayangkan semua bahasa/kompiler yang dapat dibanggakan oleh merek Wasm jika hanya ANSI C yang merupakan tes lakmus compat!

@neelance @darkuranium @d4tocchini tidak semua kontributor Wasm berpikir kurangnya goto adalah hal yang benar, pada kenyataannya, saya pribadi menilainya sebagai kesalahan desain #1 Wasm. Saya benar-benar mendukung untuk menambahkannya (baik sebagai funclet atau secara langsung).

Namun, berdebat di utas ini tidak akan membuat gotos terjadi, dan tidak secara ajaib akan membuat semua orang yang terlibat di Wasm yakin dan melakukan pekerjaan untuk Anda. Berikut langkah-langkah yang harus dilakukan:

  1. Bergabunglah dengan Wasm CG.
  2. Seseorang menginvestasikan waktu untuk menjadi juara proposal goto. Saya merekomendasikan mulai dari proposal fungsi yang ada, karena telah dipikirkan dengan baik oleh
  3. Bantu itu didorong melalui 4 tahap proposal. Ini termasuk membuat desain yang baik untuk keberatan apa pun yang Anda hadapi, memulai diskusi, dengan tujuan untuk membuat cukup banyak orang senang sehingga Anda mendapatkan suara mayoritas saat maju melalui tahapan.

@d4tocchini Jujur, saat ini saya melihat solusi yang disarankan sebagai "jalan terbaik ke depan mengingat keadaan yang tidak dapat saya ubah" alias "solusi". Saya masih menganggap instruksi lompat/goto (atau funclet) sebagai cara yang lebih sederhana dan karenanya lebih disukai. (Masih terima kasih kepada @kripken karena telah membantu menyarankan alternatif.)

@aardappel Sejauh yang saya tahu, @sunfishcode mencoba mendorong proposal funclets dan gagal. Mengapa itu akan berbeda untuk saya?

@neelance Saya tidak berpikir @sunfishcode punya banyak waktu untuk mendorong proposal melampaui pembuatan awalnya, jadi itu "macet" daripada "gagal". Seperti yang saya coba tunjukkan, itu membutuhkan seorang juara untuk melakukan pekerjaan terus-menerus agar proposal bisa lolos.

@neelance

Terima kasih untuk testcasenya! Saya dapat mengkonfirmasi masalah yang sama secara lokal. Saya mengajukan https://bugs.chromium.org/p/v8/issues/detail?id=11237

Kita perlu mengimplementasikan relooper di Go [..] Satu kelemahan kecil adalah menambahkan ketergantungan ke binaryen untuk memproduksi binari wasm.

Btw, jika itu membantu, kita bisa membuat build library binaryen sebagai file C tunggal. Mungkin itu lebih mudah untuk diintegrasikan?

Juga, menggunakan Binaryen Anda dapat menggunakan implementasi Relooper yang ada di sana . Anda dapat melewati blok dasar IR dan membiarkannya melakukan pengulangan.

@taralx

Saya percaya algoritma stackifier LLVM lebih mudah/lebih baik,

Perhatikan bahwa tautan itu bukan tentang LLVM hulu, itu adalah kompiler Cheerp (yang merupakan cabang dari LLVM). Stackifier mereka memiliki nama yang mirip dengan LLVM, tetapi berbeda.

Perhatikan juga bahwa pos Cheerp mengacu pada algoritme asli dari 2011 - implementasi relooper modern (seperti yang disebutkan sebelumnya) tidak memiliki masalah yang mereka sebutkan selama bertahun-tahun. Saya tidak mengetahui alternatif yang lebih sederhana atau lebih baik untuk pendekatan umum itu, yang sangat mirip dengan apa yang dilakukan Cheerp dan lainnya - ini adalah variasi pada sebuah tema.

@kripken Terima kasih telah mengajukan masalah ini.

Btw, jika itu membantu, kita bisa membuat build library binaryen sebagai file C tunggal. Mungkin itu lebih mudah untuk diintegrasikan?

Tidak sepertinya. Kompiler Go sendiri telah diubah menjadi Go murni beberapa waktu lalu dan afaik tidak menggunakan dependensi C lainnya. Saya tidak berpikir bahwa ini akan menjadi pengecualian.

Inilah status proposal fungsi saat ini: Langkah selanjutnya dalam proses ini adalah meminta pemungutan suara CG untuk memasuki tahap 1.

Saya sendiri saat ini fokus pada area lain di WebAssembly dan tidak memiliki bandwidth untuk mendorong funclet ke depan; jika ada yang tertarik untuk mengambil alih peran Champion untuk funclet, saya akan dengan senang hati menyerahkannya.

Tidak sepertinya. Kompiler Go sendiri telah diubah menjadi Go murni beberapa waktu lalu dan afaik tidak menggunakan dependensi C lainnya. Saya tidak berpikir bahwa ini akan menjadi pengecualian.

Selain itu, ini tidak menyelesaikan masalah penggunaan relooper secara ekstensif yang menyebabkan penurunan kinerja yang serius di runtime WebAssembly.

@Vurich

Saya pikir itu bisa menjadi kasus terbaik untuk menambahkan gotos ke wasm, tetapi seseorang perlu mengumpulkan data menarik dari kode dunia nyata yang menunjukkan tebing kinerja yang serius. Saya sendiri belum pernah melihat data seperti itu. Pekerjaan menganalisis defisit kinerja seperti "Tidak Begitu Cepat: Menganalisis Kinerja WebAssembly vs. Kode Asli" (2019) juga tidak mendukung aliran kontrol menjadi faktor yang signifikan (mereka mencatat jumlah instruksi cabang yang lebih besar, tetapi itu tidak karena aliran kontrol terstruktur - melainkan karena pemeriksaan keamanan).

@kripken Apakah Anda punya saran tentang bagaimana orang bisa mengumpulkan data seperti itu? Bagaimana cara menunjukkan bahwa defisit kinerja disebabkan oleh aliran kontrol terstruktur?

Tidak mungkin ada banyak pekerjaan yang menganalisis kinerja tahap kompilasi, yang merupakan bagian dari keluhan di sini.

Saya agak terkejut bahwa kami belum memiliki konstruksi switch case, tetapi funclets menggolongkannya.

@neelance

Tidak mudah untuk mengetahui penyebab spesifiknya, ya. Misalnya pemeriksaan batas, Anda dapat menonaktifkannya di VM dan mengukurnya, tetapi sayangnya tidak ada cara sederhana untuk melakukan hal yang sama untuk gotos.

Salah satu opsinya adalah membandingkan kode mesin yang dipancarkan dengan tangan, yang mereka lakukan di kertas yang ditautkan itu.

Pilihan lain adalah mengkompilasi wasm ke sesuatu yang Anda yakini dapat menangani aliran kontrol secara optimal, yaitu, "membatalkan" penataan. LLVM seharusnya bisa melakukan itu, jadi menjalankan wasm di VM yang menggunakan LLVM (seperti WAVM atau wasmer) atau melalui WasmBoxC bisa jadi menarik. Anda mungkin dapat menonaktifkan pengoptimalan CFG di LLVM dan melihat seberapa penting hal itu.

@taralx

Menarik, apakah saya melewatkan sesuatu tentang waktu kompilasi atau penggunaan memori? Aliran kontrol terstruktur sebenarnya harus lebih baik di sana - misalnya sangat mudah untuk pergi ke formulir SSA dari itu, dibandingkan dengan dari CFG umum. Ini sebenarnya salah satu alasan mengapa aliran kontrol terstruktur di tempat pertama. Itu juga diukur dengan sangat hati-hati karena mempengaruhi waktu muat di Web.

(Atau maksud Anda kinerja kompiler pada mesin pengembang? Memang benar bahwa wasm condong ke arah melakukan lebih banyak pekerjaan di sana, dan lebih sedikit pada klien.)

Maksud saya kompilasi kinerja di embedder, tetapi tampaknya itu diperlakukan sebagai bug , belum tentu masalah kinerja murni?

@taralx

Ya, saya pikir itu bug. Itu hanya terjadi dalam satu tingkat pada satu VM. Dan tidak ada alasan mendasar untuk itu - aliran kontrol terstruktur tidak memerlukan lebih banyak sumber daya, seharusnya membutuhkan lebih sedikit. Artinya, saya berani bertaruh bug perf seperti itu akan lebih mungkin terjadi jika wasm memang memiliki gotos.

@kripken

Aliran kontrol terstruktur sebenarnya harus lebih baik di sana - misalnya sangat mudah untuk pergi ke formulir SSA dari itu, dibandingkan dengan dari CFG umum. Ini sebenarnya salah satu alasan mengapa aliran kontrol terstruktur di tempat pertama. Itu juga diukur dengan sangat hati-hati karena mempengaruhi waktu muat di Web.

Pertanyaan yang sangat spesifik, untuk berjaga-jaga: Apakah Anda tahu ada kompiler Wasm yang benar-benar melakukan itu - "sangat sederhana" beralih dari "aliran kontrol terstruktur" ke bentuk SSA. Karena dari pandangan sekilas, aliran kontrol Wasm tidak terstruktur (sepenuhnya/akhirnya). Kontrol terstruktur secara formal adalah kontrol di mana tidak ada break s, continue s, return s (kira-kira, model pemrograman Skema, tanpa sihir seperti panggilan/cc). Ketika mereka hadir, aliran kontrol seperti itu secara kasar dapat disebut "semi-terstruktur".

Ada algo SSA yang terkenal untuk aliran kontrol yang sepenuhnya terstruktur: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.45.4503 . Inilah yang dikatakan tentang aliran kontrol semi-terstruktur:

Untuk pernyataan terstruktur, kami telah menunjukkan cara menghasilkan formulir SSA dan pohon dominator dalam satu lintasan selama penguraian. Pada bagian berikut, kami akan menunjukkan bahwa adalah mungkin untuk memperluas metode kami ke kelas pernyataan tidak terstruktur tertentu (LOOP/EXIT dan RETURN) yang dapat menyebabkan keluar dari struktur kontrol pada titik yang berubah-ubah. Namun, karena pintu keluar seperti itu adalah semacam goto (disiplin), tidak mengherankan bahwa mereka jauh lebih sulit untuk ditangani daripada pernyataan terstruktur.

OTOH, ada algoritma terkenal lainnya, https://pp.info.uni-karlsruhe.de/uploads/publikationen/braun13cc.pdf yang bisa dibilang juga single-pass, tetapi tidak memiliki masalah tidak hanya dengan kontrol non-terstruktur aliran, tetapi bahkan dengan aliran kontrol yang tidak dapat direduksi (walaupun tidak menghasilkan hasil yang optimal untuk itu).

Jadi, pertanyaannya lagi apakah Anda tahu bahwa beberapa proyek mengalami kesulitan untuk benar-benar memperluas algoritma Brandis/Mössenböck, dan mencapai manfaat nyata pada rute itu dibandingkan dengan Braun et al. algoritma (sebagai catatan tambahan, firasat intuitif saya adalah bahwa algo Braun persis seperti ekstensi "batas atas", meskipun saya terlalu bodoh untuk membuktikannya secara intuitif kepada diri saya sendiri, tidak berbicara tentang bukti formal, jadi itu saja - firasat intuitif ).

Dan tema umum pertanyaannya adalah untuk menetapkan (meskipun saya akan mengatakan "mempertahankan") alasan utama mengapa Wasm memilih keluar dari dukungan goto yang sewenang-wenang. Karena menonton utas ini selama bertahun-tahun, model mental yang saya bangun adalah hal itu dilakukan untuk menghindari menghadapi CFG yang tidak dapat direduksi . Dan memang, jurang terletak antara CFG yang dapat direduksi dan tidak dapat direduksi , dengan banyak algoritme pengoptimalan yang (jauh) lebih mudah untuk CFG yang dapat direduksi , dan itulah yang dikodekan oleh banyak pengoptimal. Aliran kontrol terstruktur (Semi) di Wasm hanyalah cara murah untuk menjamin reducibility.

Menyebutkan kemudahan khusus produksi SSA untuk CFG terstruktur (dan CFG Wasm tampaknya tidak benar-benar terstruktur dalam arti formal) entah bagaimana mengaburkan gambaran yang jelas di atas. Itu sebabnya saya bertanya apakah ada referensi khusus bahwa konstruksi SSA secara praktis diuntungkan oleh formulir CFG Wasm.

Terima kasih.

@kripken Saya agak bingung sekarang dan ingin belajar. Saya melihat situasi dan saat ini saya melihat yang berikut:


Sumber program Anda memiliki aliran kontrol tertentu. CFG ini dapat direduksi atau tidak, misalnya goto telah digunakan dalam bahasa sumber atau tidak. Tidak ada cara untuk mengubah fakta ini. CFG ini dapat diubah menjadi kode mesin, misalnya seperti yang dilakukan oleh compiler Go secara native.

Jika CFG sudah dapat direduksi, maka semuanya baik-baik saja dan VM wasm dapat memuatnya dengan cepat. Setiap terjemahan harus dapat mendeteksi bahwa ini adalah kasus sederhana dan melakukan hal yang cepat. Mengizinkan CFG yang tidak dapat direduksi seharusnya tidak memperlambat kasus ini.

Jika CFG tidak dapat direduksi, maka ada dua opsi:

  • Kompilator membuatnya dapat direduksi, misalnya dengan memperkenalkan tabel lompat. Langkah ini kehilangan informasi. Sulit untuk mengembalikan CFG asli tanpa analisis yang khusus untuk kompiler yang menghasilkan biner. Karena hilangnya informasi ini, setiap kode mesin yang dihasilkan akan lebih lambat daripada kode yang dihasilkan dari CFG awal. Kami mungkin dapat membuat kode mesin ini dengan algoritma single pass, tetapi dengan biaya kehilangan informasi. [1]

  • Kami mengizinkan kompiler untuk memancarkan CFG yang tidak dapat direduksi. VM mungkin harus membuatnya dapat direduksi. Ini memperlambat waktu buka, tetapi hanya dalam kasus di mana CFG sebenarnya tidak dapat direduksi. Kompiler memiliki opsi untuk memilih antara mengoptimalkan kinerja waktu buka atau kinerja waktu berjalan.

[1] Saya sadar bahwa itu bukan kehilangan informasi jika masih ada cara untuk membalikkan operasi, tetapi saya tidak dapat menggambarkannya dengan cara yang lebih baik.


Di mana letak kesalahan dalam pemikiran saya?

@pfalcon

Apakah Anda tahu ada kompiler Wasm yang benar-benar melakukan itu - "sangat sederhana" beralih dari "aliran kontrol terstruktur" ke bentuk SSA.

Tentang VM: Saya tidak tahu secara langsung. Tetapi IIRC di masa lalu @titzer dan @lukewagner mengatakan nyaman untuk menerapkan cara itu - mungkin salah satu dari mereka dapat menguraikannya. Saya tidak yakin apakah ireduksibilitas adalah keseluruhan masalah di sana atau tidak. Dan saya tidak yakin apakah mereka telah menerapkan algoritme yang Anda sebutkan atau tidak.

Tentang hal-hal selain VM: Pengoptimal Binaryen pasti mendapat manfaat dari aliran kontrol terstruktur wasm, dan bukan hanya dapat direduksi. Berbagai pengoptimalan lebih sederhana karena kita selalu tahu di mana header loop berada, misalnya, yang dianotasi di wasm. (Pengoptimalan OTOH lainnya lebih sulit dilakukan, dan kami juga memiliki IR CFG umum untuk itu...)

@neelance

Jika CFG sudah dapat direduksi, maka semuanya baik-baik saja dan VM wasm dapat memuatnya dengan cepat. Setiap terjemahan harus dapat mendeteksi bahwa ini adalah kasus sederhana dan melakukan hal yang cepat. Mengizinkan CFG yang tidak dapat direduksi seharusnya tidak memperlambat kasus ini.

Mungkin aku belum sepenuhnya memahamimu. Tetapi bahwa VM wasm dapat memuat kode dengan cepat tidak hanya bergantung pada apakah kode tersebut dapat direduksi atau tidak, tetapi juga pada bagaimana kode tersebut dikodekan. Secara khusus, kami dapat membayangkan format yang merupakan CFG umum, dan kemudian VM perlu melakukan pekerjaan untuk memverifikasi bahwa itu dapat direduksi. Wasm memilih untuk menghindari pekerjaan itu - penyandian harus dapat direduksi (yaitu, saat Anda membaca wasm dan melakukan validasi sepele, Anda juga membuktikan bahwa itu dapat direduksi tanpa melakukan pekerjaan tambahan).

Selain itu, penyandian wasm tidak hanya memberikan jaminan dapat direduksi tanpa perlu memverifikasinya. Itu juga membubuhi keterangan header loop, ifs, dan hal-hal berguna lainnya (seperti yang saya sebutkan secara terpisah sebelumnya dalam komentar ini). Saya tidak yakin begitu saja berapa banyak VM produksi mendapat manfaat dari itu, tetapi saya berharap mereka melakukannya. (Mungkin terutama di kompiler dasar?)

Secara keseluruhan saya pikir mengizinkan CFG yang tidak dapat direduksi dapat memperlambat kasus cepat, kecuali yang tidak dapat direduksi dikodekan secara terpisah (seperti fungsi yang diusulkan).

@kripken

Terima kasih atas penjelasan Anda.

Ya, inilah diferensiasi yang saya coba buat: Saya melihat keuntungan dari notasi/pengkodean terstruktur untuk kasus CFG yang dapat direduksi. Tetapi seharusnya tidak sulit untuk menambahkan beberapa konstruk yang memungkinkan notasi CFG yang tidak dapat direduksi dan tetap mempertahankan keunggulan yang ada dalam hal CFG sumber yang dapat direduksi (misalnya jika Anda tidak menggunakan konstruksi baru ini, maka CFG dijamin dapat direduksi).

Sebagai kesimpulan, saya tidak melihat bagaimana orang dapat berargumen bahwa notasi yang murni dapat direduksi lebih cepat. Dalam kasus CFG sumber yang dapat direduksi, kecepatannya sama cepatnya. Dan dalam kasus jika sumber CFG yang tidak dapat direduksi paling banyak dapat dikatakan bahwa itu tidak secara signifikan lebih lambat, tetapi beberapa kasus dunia nyata telah menunjukkan bahwa ini tidak mungkin terjadi secara umum.

Singkatnya, saya tidak melihat bagaimana pertimbangan kinerja dapat menjadi argumen yang mencegah aliran kontrol yang tidak dapat direduksi dan itu membuat saya mempertanyakan mengapa langkah selanjutnya perlu mengumpulkan data kinerja.

@neelance

Ya, saya setuju bahwa kita dapat menambahkan konstruksi baru - seperti fungsi - dan dengan tidak menggunakannya, itu tidak akan memperlambat kasus yang ada.

Tetapi ada kerugian untuk menambahkan konstruksi baru karena menambah kompleksitas pada wasm. Secara khusus ini berarti area permukaan yang lebih besar pada VM yang berarti lebih banyak kemungkinan bug dan masalah keamanan. Wasm telah condong ke arah memiliki sebanyak mungkin kompleksitas di sisi pengembang jika memungkinkan untuk mengurangi kompleksitas pada VM.

Beberapa proposal wasm bukan hanya tentang kecepatan, seperti GC (yang memungkinkan pengumpulan siklus dengan JS). Tetapi untuk proposal tentang kecepatan, seperti funclet, kita perlu menunjukkan bahwa kecepatan membenarkan kerumitan. Kami berdebat tentang SIMD yang juga tentang kecepatan, dan memutuskan bahwa ini sepadan karena kami melihatnya dapat dengan andal mencapai percepatan yang sangat besar pada kode dunia nyata (2x atau bahkan lebih).

(Ada manfaat lain selain kecepatan untuk mengizinkan CFG umum, saya setuju, seperti mempermudah kompiler untuk menargetkan wasm. Tapi kami bisa menyelesaikannya tanpa menambahkan kerumitan ke VM wasm. Kami sudah menyediakan dukungan untuk CFG arbitrer di LLVM dan Binaryen , memungkinkan kompiler untuk memancarkan CFG dan tidak khawatir tentang aliran kontrol terstruktur. Jika itu tidak cukup baik, kami - alat orang yang saya maksud, termasuk saya - harus berbuat lebih banyak.)

Funclet bukan tentang kecepatan, tetapi tentang mengizinkan bahasa dengan aliran kontrol non-sepele untuk dikompilasi ke WebAssembly, C dan Go menjadi yang paling jelas tetapi berlaku untuk bahasa apa pun yang memiliki async/menunggu. Juga, pilihan untuk memiliki aliran kontrol hierarkis sebenarnya mengarah ke _more_ bug di VM, sebagaimana dibuktikan oleh fakta bahwa semua kompiler Wasm selain V8 menguraikan aliran kontrol hierarkis ke CFG. EBB dalam CFG dapat mewakili beberapa konstruksi aliran kontrol di Wasm dan lebih banyak lagi, dan memiliki satu konstruksi untuk dikompilasi menyebabkan jauh lebih sedikit bug daripada memiliki banyak jenis berbeda dengan kegunaan berbeda.

Bahkan Lightbeam, kompilator streaming yang sangat sederhana, melihat penurunan besar-besaran dalam kesalahan kompilasi bug setelah menambahkan langkah terjemahan ekstra yang menguraikan aliran kontrol ke CFG. Ini berlaku dua kali lipat untuk sisi lain dari proses ini - Relooper jauh lebih rawan kesalahan daripada memancarkan funclet, dan saya telah diberitahu oleh pengembang yang mengerjakan backend Wasm untuk LLVM dan kompiler lain yang berfungsi untuk diimplementasikan, mereka akan memancarkan setiap fungsi menggunakan funclet saja, untuk meningkatkan keandalan dan kesederhanaan codegen. Semua kompiler yang memproduksi Wasm menggunakan EBB, semua kecuali satu kompiler yang menggunakan Wasm menggunakan EBB, penolakan untuk mengimplementasikan funclet atau cara lain untuk merepresentasikan CFG hanyalah menambahkan langkah yang merugikan di antaranya yang merugikan semua pihak yang terlibat selain tim V8 .

"Alur kontrol yang tidak dapat direduksi dianggap berbahaya" hanyalah poin pembicaraan, Anda dapat dengan mudah menambahkan batasan bahwa aliran kontrol fungsi dapat direduksi dan kemudian jika Anda ingin mengizinkan aliran kontrol yang tidak dapat direduksi di masa mendatang, semua modul Wasm yang ada dengan aliran kontrol yang dapat direduksi akan bekerja tanpa dimodifikasi pada mesin yang juga mendukung aliran kontrol tak tereduksi. Ini hanya akan menjadi kasus menghapus pemeriksaan reducibility di validator.

@Vurich

Anda dapat dengan mudah menambahkan batasan agar aliran kontrol fungsi dapat direduksi

Anda bisa, tetapi itu tidak sepele - VM perlu memverifikasi itu. Saya tidak berpikir itu mungkin dalam satu lintasan linier, yang akan menjadi masalah bagi kompiler dasar, yang sekarang ada di sebagian besar VM. (Faktanya, hanya menemukan backedge loop - yang merupakan masalah yang lebih sederhana, dan juga diperlukan untuk alasan lain - tidak dapat dilakukan dalam satu umpan maju, bukan?)

semua kompiler Wasm selain V8 menguraikan aliran kontrol hierarkis ke CFG.

Apakah Anda mengacu pada pendekatan "lautan simpul" yang digunakan TurboFan? Saya bukan ahli dalam hal itu jadi saya akan menyerahkannya kepada orang lain untuk menanggapi.

Tetapi secara lebih umum, bahkan jika Anda tidak membeli argumen di atas untuk mengoptimalkan kompiler, itu bahkan lebih langsung berlaku untuk kompiler dasar, seperti yang disebutkan sebelumnya.

Funclet bukan tentang kecepatan, tetapi tentang mengizinkan bahasa dengan aliran kontrol non-sepele untuk dikompilasi ke WebAssembly [..] Relooper jauh lebih rawan kesalahan daripada memancarkan funclet

Saya setuju 100% di sisi alat. Lebih sulit untuk memancarkan kode terstruktur dari sebagian besar kompiler! Tapi intinya adalah membuatnya lebih sederhana di sisi VM, dan itulah yang dipilih untuk dilakukan. Tetapi sekali lagi, saya setuju bahwa ini memiliki pengorbanan, termasuk kerugian yang Anda sebutkan.

Apakah saya salah melakukan ini pada tahun 2015? Itu mungkin. Saya pikir kami sendiri memiliki beberapa kesalahan (seperti kemampuan debugg, dan keterlambatan beralih ke mesin tumpukan). Tetapi tidak mungkin untuk memperbaikinya dalam retrospeksi, dan ada batasan tinggi untuk menambahkan hal-hal baru, terutama yang tumpang tindih.

Mengingat semua itu, mencoba konstruktif, saya pikir kita harus memperbaiki masalah yang ada di sisi alat. Ada bar yang jauh lebih rendah untuk perubahan alat. Dua kemungkinan saran:

  • Saya dapat melihat porting kode CFG Binaryen ke Go, apakah itu akan membantu kompiler Go - @neelance ?
  • Kami dapat menerapkan funclet atau sesuatu seperti itu murni di sisi alat. Artinya, kami menyediakan kode perpustakaan untuk hari ini, tetapi juga dapat menambahkan format biner. (Sudah ada preseden untuk menambahkan format biner wasm di sisi alat, dalam file objek wasm.)

Kami dapat menerapkan funclet atau sesuatu seperti itu murni di sisi alat. Artinya, kami menyediakan kode perpustakaan untuk hari ini, tetapi juga dapat menambahkan format biner. (Sudah ada preseden untuk menambahkan format biner wasm di sisi alat, dalam file objek wasm.)

Jika ada pekerjaan nyata yang dilakukan dalam hal ini, perlu dicatat bahwa (AFAIU) cara idiomatik terkecil untuk menambahkan ini ke Wasm (seperti yang disinggung oleh @rossberg ) adalah dengan memperkenalkan instruksi blok

multiloop (t masuk ) _n_ t keluar (_instr_* akhir ) _n_

yang mendefinisikan n badan berlabel (dengan n anotasi tipe input yang dideklarasikan ke depan). Keluarga instruksi br kemudian digeneralisasi sehingga semua label yang didefinisikan oleh multiloop berada dalam lingkup di dalam setiap badan, secara berurutan (seperti dalam, badan apa pun dapat bercabang dari dalam badan lain mana pun). Saat badan multiloop bercabang, eksekusi melompat ke _start_ badan (seperti loop Wasm biasa). Ketika eksekusi mencapai akhir sebuah badan tanpa bercabang ke badan lain, seluruh konstruksi kembali (tidak ada fall-through).

Akan ada beberapa pelepasan sepeda yang harus dilakukan tentang bagaimana merepresentasikan tipe anotasi setiap bodi secara efisien (dalam formulasi di atas, n bodi dapat memiliki n tipe input yang berbeda, tetapi semuanya harus memiliki tipe output yang sama, jadi saya tidak dapat langsung menggunakan indeks _blocktype_ multi-nilai biasa tanpa memerlukan perhitungan LUB yang berlebihan), dan bagaimana memilih badan awal yang akan dieksekusi (selalu yang pertama, atau haruskah ada parameter statis?).

Ini mendapatkan tingkat ekspresivitas yang sama dengan funclet tetapi menghindari keharusan untuk memperkenalkan ruang instruksi kontrol baru. Sebenarnya jika funclet telah diulang lebih jauh, saya pikir itu akan berubah menjadi seperti ini.

EDIT: mengutak-atik ini untuk memiliki perilaku fall-through akan sedikit memperumit semantik formal, tetapi mungkin akan lebih baik untuk kasus penggunaan @neelance , dan dapat membantu mengisyaratkan kepada kompiler dasar apa jalur aliran kontrol on-trace itu.

Prinsip desain Wasm dari pekerjaan pembongkaran ke alat untuk membuat mesin lebih sederhana/cepat sangat penting, dan akan terus sangat bermanfaat.

Yang mengatakan, seperti segala sesuatu yang non-sepele, ini adalah trade-off, bukan hitam dan putih. Saya percaya di sini kita memiliki kasus di mana rasa sakit bagi produsen tidak sebanding dengan rasa sakit untuk mesin. Sebagian besar kompiler yang ingin kami bawa ke Wasm menggunakan struktur CFG sewenang-wenang secara internal (SSA) atau digunakan untuk menargetkan hal-hal yang tidak keberatan dengan gotos (CPU). Kami membuat dunia melompat melalui lingkaran untuk tidak banyak keuntungan.

Sesuatu seperti funclet (atau multiloop) bagus karena bersifat modular: jika produser tidak membutuhkannya maka semuanya akan berfungsi seperti sebelumnya. Jika mesin benar-benar tidak dapat menangani CFG sewenang-wenang maka untuk saat ini mereka dapat memancarkannya seolah-olah itu adalah jenis konstruksi loop + br_table , dan hanya mereka yang menggunakannya yang membayar harganya . Kemudian, "pasar memutuskan" dan kami melihat apakah ada tekanan pada mesin untuk mengeluarkan kode yang lebih baik untuk itu. Sesuatu memberi tahu saya bahwa jika akan ada banyak kode Wasm yang bergantung pada fungsi, itu tidak akan menjadi bencana besar bagi mesin untuk memancarkan kode yang baik untuk mereka seperti yang tampaknya dipikirkan beberapa orang.

Anda bisa, tetapi itu tidak sepele - VM perlu memverifikasi itu. Saya tidak berpikir itu mungkin dalam satu lintasan linier, yang akan menjadi masalah bagi kompiler dasar, yang sekarang ada di sebagian besar VM.

Mungkin saya salah memahami harapan untuk kompiler dasar, tetapi mengapa mereka peduli? Jika Anda melihat goto, masukkan instruksi lompat.

Saya setuju 100% di sisi alat. Lebih sulit untuk memancarkan kode terstruktur dari sebagian besar kompiler! Tapi intinya adalah membuatnya lebih sederhana di sisi VM, dan itulah yang dipilih untuk dilakukan. Tetapi sekali lagi, saya setuju bahwa ini memiliki pengorbanan, termasuk kerugian yang Anda sebutkan.

Tidak, seperti yang saya katakan berkali-kali dalam komentar asli saya, itu _tidak_ membuat segalanya lebih mudah di sisi VM. Saya bekerja pada kompiler dasar selama lebih dari setahun dan hidup saya menjadi lebih mudah dan kode yang dipancarkan menjadi lebih cepat setelah saya menambahkan langkah sementara yang mengubah aliran kontrol Wasm menjadi CFG.

Anda bisa, tetapi itu tidak sepele - VM perlu memverifikasi itu. Saya tidak berpikir itu mungkin dalam satu lintasan linier, yang akan menjadi masalah bagi kompiler dasar, yang sekarang ada di sebagian besar VM. (Faktanya, hanya menemukan backedge loop - yang merupakan masalah yang lebih sederhana, dan juga diperlukan untuk alasan lain - tidak dapat dilakukan dalam satu umpan maju, bukan?)

Ok inilah masalahnya, pengetahuan saya tentang algoritma yang digunakan dalam kompiler tidak cukup kuat untuk menyatakan dengan kepastian mutlak bahwa aliran kontrol yang tidak dapat direduksi dapat atau tidak dapat dideteksi dalam kompiler streaming, tetapi masalahnya adalah itu tidak perlu. Verifikasi dapat terjadi bersamaan dengan kompilasi. Jika algoritme streaming tidak ada, yang baik Anda maupun saya tidak tahu, Anda dapat menggunakan algoritme non-streaming setelah fungsi diterima sepenuhnya. Jika (karena alasan tertentu) aliran kontrol yang tidak dapat direduksi mengarah ke sesuatu yang benar-benar buruk seperti infinite loop, Anda dapat dengan mudah mengatur waktu kompilasi dan/atau membatalkan utas kompilasi. Namun, tidak ada alasan untuk percaya bahwa ini akan terjadi.

Mungkin saya salah memahami harapan untuk kompiler dasar, tetapi mengapa mereka peduli? Jika Anda melihat goto, masukkan instruksi lompat.

Ini tidak sesederhana itu karena bagaimana Anda perlu memetakan mesin register tak terbatas Wasm (tidak, ini bukan mesin tumpukan ) ke register terbatas perangkat keras fisik, tetapi itu adalah masalah yang harus diselesaikan oleh kompiler streaming mana pun dan sepenuhnya ortogonal untuk CFG vs aliran kontrol hierarkis.

Kompiler streaming yang saya kerjakan dapat mengkompilasi CFG yang sewenang-wenang - bahkan tidak dapat direduksi - dengan baik. Itu tidak melakukan sesuatu yang istimewa. Anda cukup menetapkan setiap blok "konvensi panggilan" (pada dasarnya tempat di mana nilai-nilai dalam cakupan di blok itu seharusnya) ketika Anda pertama kali harus melompat ke sana, dan jika Anda pernah mencapai titik di mana Anda perlu bercabang ke dua atau lebih target dengan "konvensi panggilan" yang tidak kompatibel, Anda mendorong blok "adaptor" ke antrian dan memancarkannya pada titik berikutnya yang memungkinkan. Ini dapat terjadi baik dengan aliran kontrol yang dapat direduksi dan tidak dapat direduksi, dan hampir tidak pernah diperlukan dalam kedua kasus tersebut. Argumen "aliran kontrol yang tidak dapat direduksi dianggap berbahaya", seperti yang saya katakan sebelumnya, adalah pokok pembicaraan dan bukan argumen teknis. Mewakili aliran kontrol sebagai CFG membuat kompiler streaming jauh lebih mudah untuk ditulis, dan seperti yang telah saya katakan berkali-kali, saya tahu ini dari pengalaman pribadi yang luas.

Setiap kasus di mana aliran kontrol yang tidak dapat direduksi membuat implementasi lebih sulit untuk ditulis, yang tidak dapat saya pikirkan, hanya dapat dimatikan dan mengembalikan kesalahan, dan jika Anda memerlukan algoritme non-streaming terpisah untuk mendeteksi 100% yakin bahwa kontrol flow tidak dapat direduksi (jadi Anda tidak secara tidak sengaja menerima aliran kontrol yang tidak dapat direduksi) maka itu dapat berjalan secara terpisah dari kompiler baseline itu sendiri. Saya telah diberitahu oleh seseorang yang saya memiliki alasan untuk percaya adalah otoritas pada subjek (walaupun saya akan menghindari memanggil mereka karena saya tahu mereka tidak ingin diseret ke dalam utas ini) bahwa ada algoritme streaming yang relatif sederhana untuk mendeteksi ireduksibilitas CFG, tetapi saya tidak dapat mengatakan secara langsung bahwa ini benar.

@oridb

Mungkin saya salah memahami harapan untuk kompiler dasar, tetapi mengapa mereka peduli? Jika Anda melihat goto, masukkan instruksi lompat.

Kompilator dasar masih perlu melakukan hal-hal seperti menyisipkan pemeriksaan ekstra di backedge loop (begitulah di Web halaman gantung pada akhirnya akan menampilkan dialog skrip yang lambat), jadi mereka perlu mengidentifikasi hal-hal seperti itu. Juga, mereka mencoba melakukan alokasi register yang cukup efisien (kompiler dasar sering berjalan sekitar 1/2 kecepatan kompiler pengoptimal - yang sangat mengesankan mengingat mereka single-pass!). Memiliki struktur aliran kontrol, termasuk penggabungan dan pemisahan, membuatnya lebih mudah.

@gwvo

Yang mengatakan, seperti segala sesuatu yang non-sepele, ini adalah trade-off, bukan hitam dan putih. [..] Kami membuat dunia melompat melalui lingkaran untuk tidak banyak keuntungan.

Sangat setuju bahwa ini adalah tradeoff, dan bahkan mungkin salah saat itu. Tapi saya percaya jauh lebih praktis untuk memperbaiki lingkaran itu di sisi alat.

Kemudian, "pasar memutuskan" dan kami melihat apakah ada tekanan pada mesin untuk mengeluarkan kode yang lebih baik untuk itu.

Ini sebenarnya sesuatu yang kita hindari sejauh ini. Kami telah mencoba membuat wasm sesederhana mungkin pada VM sehingga tidak memerlukan pengoptimalan yang rumit - bahkan hal-hal seperti inlining, sebisa mungkin. Tujuannya adalah untuk melakukan kerja keras di sisi alat, bukan untuk menekan VM agar bekerja lebih baik.

@Vurich

Saya bekerja pada kompiler dasar selama lebih dari setahun dan hidup saya menjadi lebih mudah dan kode yang dipancarkan menjadi lebih cepat setelah saya menambahkan langkah sementara yang mengubah aliran kontrol Wasm menjadi CFG.

Sangat menarik! VM yang mana itu?

Saya juga secara khusus ingin tahu apakah itu single-pass/streaming atau tidak (jika ya, bagaimana cara menangani instrumentasi backedge loop?), dan bagaimana cara mendaftar alokasi.

Pada prinsipnya, backedge loop dan alokasi register dapat ditangani berdasarkan urutan instruksi linier, dengan harapan bahwa blok dasar akan diletakkan dalam urutan seperti topsort yang masuk akal, tanpa benar-benar membutuhkannya.

Untuk loop backedge: Definisikan backedge sebagai instruksi yang melompat ke sebelumnya dalam aliran instruksi. Paling buruk, jika blok diletakkan terbalik, Anda mendapatkan lebih banyak pemeriksaan backedge daripada yang dibutuhkan.

Untuk alokasi register: Ini hanya alokasi register pemindaian linier standar. Umur variabel untuk alokasi register terbentang dari penyebutan pertama variabel hingga penyebutan terakhir, termasuk semua blok yang secara linier di antaranya. Paling buruk, jika blok dikocok, Anda mendapatkan masa pakai lebih lama dari yang dibutuhkan dan dengan demikian tidak perlu menumpahkan barang ke tumpukan. Satu-satunya biaya tambahan adalah melacak penyebutan pertama dan terakhir setiap variabel, yang dapat dilakukan untuk semua variabel dengan pemindaian linier tunggal. (Untuk wasm, saya kira "variabel" adalah slot lokal atau tumpukan.)

@kripken

Saya dapat melihat porting kode CFG Binaryen ke Go, apakah itu akan membantu kompiler Go - @neelance ?

Untuk mengintegrasikan Asyncify? Silahkan komentari proposalnya .

@comex

Poin bagus!

Satu-satunya biaya tambahan adalah melacak penyebutan pertama dan terakhir dari setiap variabel

Ya, saya pikir itu perbedaan yang signifikan. Alokasi register pemindaian linier lebih baik (tetapi lebih lambat untuk dilakukan) daripada apa yang dilakukan oleh kompiler dasar wasm saat ini , karena mereka mengkompilasi dengan cara streaming yang sangat cepat. Artinya, tidak ada langkah awal untuk menemukan penyebutan terakhir dari setiap variabel - mereka mengkompilasi dalam satu pass, memancarkan kode saat mereka pergi bahkan tanpa melihat kode nanti dalam fungsi wasm, dibantu oleh struktur, dan juga mereka membuat sederhana pilihan saat mereka pergi ("bodoh" adalah kata yang digunakan dalam posting itu).

Pendekatan streaming V8 untuk mendaftarkan alokasi harus berfungsi dengan baik jika blok diizinkan untuk saling rekursif (seperti dalam https://github.com/WebAssembly/design/issues/796#issuecomment-742690194), karena satu-satunya masa hidup yang mereka tangani terikat dalam satu blok (tumpukan) atau dianggap sebagai fungsi-lebar (lokal).

IIUC (dengan mengacu pada komentar @titzer ) masalah utama untuk V8 terletak pada jenis CFG yang dapat dioptimalkan oleh Turbofan.

@kripken

Kami telah mencoba membuat wasm sesederhana mungkin di VM sehingga tidak memerlukan pengoptimalan yang rumit

Ini bukan "optimasi kompleks".. gotos sangat mendasar dan alami untuk banyak sistem. Saya yakin ada banyak mesin yang dapat menambahkan ini tanpa biaya. Saya hanya mengatakan bahwa jika ada mesin yang ingin mempertahankan model CFG terstruktur untuk alasan apa pun, mereka bisa.

Misalnya, saya cukup yakin LLVM (sejauh ini produsen Wasm # 1 kami saat ini) tidak akan beralih menggunakan funclet sampai yakin bahwa itu bukan regresi kinerja di mesin utama.

@kripken Itu bagian dari Wasmtime. Ya, itu streaming dan dimaksudkan untuk menjadi kompleksitas O(N), tetapi saya pindah ke perusahaan baru sebelum itu terwujud sepenuhnya sehingga hanya "O(N)-ish". https://github.com/bytecodealliance/wasmtime/tree/main/crates/lightbeam

Terima kasih @Vurich , menarik. Akan sangat bagus untuk melihat angka perf ketika itu tersedia, terutama untuk startup tetapi juga throughput. Saya kira pendekatan Anda akan dikompilasi lebih lambat daripada pendekatan yang diambil oleh para insinyur V8 dan SpiderMonkey, sambil memancarkan kode yang lebih cepat. Jadi ada tradeoff yang berbeda di ruang ini. Tampaknya masuk akal bahwa pendekatan Anda tidak mendapat manfaat dari aliran kontrol terstruktur wasm, seperti yang Anda katakan, sementara milik mereka.

Tidak, ini adalah kompiler streaming dan memancarkan kode lebih cepat daripada salah satu dari kedua mesin itu (walaupun ada kasus yang merosot yang tidak diperbaiki pada saat saya meninggalkan proyek). Sementara saya melakukan yang terbaik untuk memancarkan kode cepat, itu terutama dirancang untuk memancarkan kode dengan cepat dengan efisiensi output menjadi perhatian kedua. Biaya startup, sepengetahuan saya, nol (di atas biaya bawaan Wasmtime yang dibagi di antara backend) karena setiap struktur data mulai tidak diinisialisasi dan kompilasi dilakukan instruksi demi instruksi. Meskipun saya tidak memiliki angka untuk dibandingkan dengan V8 atau SpiderMonkey, saya memiliki angka untuk dibandingkan dengan Cranelift (mesin utama di wasmtime). Mereka beberapa bulan kedaluwarsa pada saat ini, tetapi Anda dapat melihat bahwa itu tidak hanya memancarkan kode lebih cepat daripada Cranelift, tetapi juga memancarkan kode lebih cepat daripada Cranelift. Pada saat itu, ia juga mengeluarkan kode yang lebih cepat daripada SpiderMonkey, meskipun Anda harus menuruti kata-kata saya, jadi saya tidak akan menyalahkan Anda jika Anda tidak mempercayai saya. Meskipun saya tidak memiliki nomor yang lebih baru, saya percaya bahwa keadaan sekarang adalah Cranelift dan SpiderMonkey memperbaiki segelintir kecil bug yang merupakan sumber utama dari output berkinerja rendah mereka di microbenchmark ini jika dibandingkan dengan Lightbeam, tetapi perbedaan kecepatan kompilasi tidak berubah sepanjang waktu saya mengerjakan proyek karena setiap kompiler pada dasarnya masih memiliki arsitektur yang sama, dan arsitektur masing-masing yang mengarah ke tingkat kinerja yang berbeda. Sementara saya menghargai spekulasi Anda, saya tidak tahu dari mana asumsi Anda bahwa metode yang saya uraikan akan lebih lambat berasal.

Berikut adalah tolok ukurnya, tolok ukur ::compile adalah untuk kecepatan kompilasi dan tolok ukur ::run adalah untuk kecepatan eksekusi keluaran kode mesin. https://Gist.github.com/Vurich/8696e67180aa3c93b4548fb1f298c29e

Metodologinya ada di sini, Anda dapat mengkloningnya dan menjalankan kembali tolok ukur untuk mengonfirmasi hasilnya sendiri tetapi PR kemungkinan besar tidak akan kompatibel dengan versi wasmtime terbaru sehingga hanya akan menampilkan perbandingan kinerja pada saat saya terakhir memperbarui PR. https://github.com/bytecodealliance/wasmtime/pull/1660

Karena itu, argumen saya adalah _not_ bahwa CFG adalah representasi internal yang berguna untuk kinerja dalam kompiler streaming. Argumen saya adalah bahwa CFG tidak secara negatif memengaruhi kinerja di kompiler apa pun, dan tentu saja tidak ke tingkat yang akan membenarkan sepenuhnya mengunci tim GCC dan Go dari memproduksi WebAssembly sama sekali. Hampir tidak ada seorang pun di utas ini yang menentang fungsi atau ekstensi serupa dengan wasm yang benar-benar mengerjakan proyek yang mereka klaim akan terpengaruh secara negatif oleh proposal ini. Bukan untuk mengatakan bahwa Anda memerlukan pengalaman langsung untuk mengomentari topik ini sama sekali, saya pikir setiap orang memiliki beberapa tingkat masukan yang berharga, tetapi untuk mengatakan bahwa ada batas antara memiliki pendapat yang berbeda tentang warna bikeshed dan pembuatan klaim berdasarkan tidak lebih dari spekulasi kosong.

@Vurich

Tidak, ini adalah kompiler streaming dan memancarkan kode lebih cepat daripada salah satu dari kedua mesin itu (walaupun ada kasus yang merosot yang tidak pernah diperbaiki karena saya meninggalkan proyek).

Maaf jika saya tidak cukup jelas sebelumnya. Yang pasti kita membicarakan hal yang sama, maksud saya kompiler dasar di mesin itu. Dan saya berbicara tentang waktu kompilasi, yang merupakan inti dari kompiler dasar dalam arti bahwa V8 dan SpiderMonkey menggunakan istilah tersebut.

Alasan saya skeptis Anda bisa mengalahkan V8 dan SpiderMonkey dasar kompilasi kali ini karena, seperti dalam satu link saya berikan sebelumnya, kedua compiler dasar yang luar biasa disetel untuk waktu kompilasi. Secara khusus mereka tidak menghasilkan IR internal, mereka hanya langsung dari wasm ke kode mesin. Anda mengatakan bahwa kompiler Anda memancarkan IR internal (untuk CFG) - Saya berharap waktu kompilasi Anda lebih lambat hanya karena itu (karena lebih banyak percabangan, bandwidth memori, dll.).

Tapi tolong tolok ukur dengan kompiler dasar itu! Saya ingin melihat data yang menunjukkan bahwa tebakan saya salah, dan saya yakin begitu juga para insinyur V8 dan SpiderMonkey. Itu berarti Anda telah menemukan desain yang lebih baik yang harus mereka pertimbangkan untuk diadopsi.

Untuk menguji terhadap V8, Anda dapat menjalankan d8 --liftoff --no-wasm-tier-up , dan untuk SpiderMonkey Anda dapat menjalankan sm --wasm-compiler=baseline .

(Terima kasih atas petunjuk untuk membandingkan dengan Cranelift, tetapi Cranelift bukan kompiler dasar, jadi membandingkan waktu kompilasi dengannya tidak relevan dalam konteks ini. Sangat menarik jika sebaliknya, saya setuju.)

Intuisi saya adalah bahwa kompiler dasar tidak perlu secara signifikan mengubah strategi kompilasi mereka untuk mendukung funclets/ multiloop , karena mereka tidak berusaha untuk melakukan optimasi antar-blok yang berarti. "Struktur aliran kontrol yang andal , termasuk direferensikan oleh @kripken dipenuhi dengan mengharuskan semua jenis input untuk kumpulan blok yang saling rekursif untuk dideklarasikan ke depan (yang tampaknya merupakan pilihan alami untuk validasi streaming) . Apakah Lightbeam/Wasmtime dapat mengalahkan kompiler dasar mesin tidak menjadi faktor dalam hal ini; poin pentingnya adalah apakah kompiler dasar mesin dapat tetap secepat sekarang.

FWIW, saya akan tertarik untuk melihat fitur ini diangkat untuk diskusi dalam pertemuan CG mendatang, dan saya secara luas setuju dengan @Vurich bahwa perwakilan mesin dapat saga JavaScript

@kripken

Ya, saya pikir itu perbedaan yang signifikan. Alokasi register pemindaian linier lebih baik (tetapi lebih lambat untuk dilakukan) daripada apa yang dilakukan oleh kompiler dasar wasm saat ini , karena mereka mengkompilasi dengan cara streaming yang sangat cepat. Artinya, tidak ada langkah awal untuk menemukan penyebutan terakhir dari setiap variabel - mereka mengkompilasi dalam satu pass, memancarkan kode saat mereka pergi bahkan tanpa melihat kode nanti dalam fungsi wasm, dibantu oleh struktur, dan juga mereka membuat sederhana pilihan saat mereka pergi ("bodoh" adalah kata yang digunakan dalam posting itu).

Wow, itu benar-benar sangat sederhana.

Di sisi lain… algoritme tertentu sangat sederhana sehingga tidak bergantung pada properti mendalam apa pun dari aliran kontrol terstruktur. Bahkan hampir tidak tergantung pada sifat dangkal aliran kontrol terstruktur.

Seperti yang disebutkan dalam posting blog, kompiler dasar wasm SpiderMonkey tidak mempertahankan status pengalokasi register melalui "penggabungan aliran kontrol" (yaitu blok dasar dengan beberapa pendahulu), alih-alih menggunakan ABI tetap, atau pemetaan dari tumpukan wasm ke tumpukan dan register asli . Saya menemukan melalui pengujian bahwa itu juga menggunakan ABI tetap saat memasukkan block , meskipun itu bukan gabungan aliran kontrol dalam banyak kasus!

ABI tetap adalah sebagai berikut (pada x86):

  • Jika ada jumlah parameter yang bukan nol (saat memasuki blok) atau kembali (saat keluar dari blok), maka bagian atas tumpukan wasm masuk rax , dan sisa tumpukan wasm sesuai dengan x86 tumpukan.
  • Jika tidak, seluruh tumpukan wasm sesuai dengan tumpukan x86.

Mengapa ini penting?

Karena algoritma ini dapat bekerja dengan cara yang hampir sama dengan informasi yang jauh lebih sedikit. Sebagai eksperimen pemikiran, bayangkan versi WebAssembly alam semesta alternatif di mana tidak ada instruksi aliran kontrol terstruktur, hanya instruksi lompat, mirip dengan perakitan asli. Itu harus ditambah dengan hanya satu informasi tambahan: cara untuk mengetahui instruksi mana yang menjadi target lompatan.

Maka algoritmenya adalah: ikuti instruksi secara linier; sebelum melompat dan melompat target, register flush ke ABI tetap.

Satu-satunya perbedaan adalah bahwa harus ada satu ABI tetap, bukan dua. Itu tidak dapat membedakan antara nilai top-of-stack yang secara semantik merupakan 'hasil' lompatan, versus hanya ditinggalkan di tumpukan dari blok luar. Jadi itu harus tanpa syarat menempatkan tumpukan teratas di rax .

Tapi saya ragu ini akan memiliki biaya terukur untuk kinerja; jika ada, itu mungkin perbaikan.

(Verifikasinya juga akan berbeda tetapi masih single-pass.)

Oke, peringatan di muka:

  1. Ini bukan alam semesta alternatif; kami terjebak dalam membuat ekstensi yang kompatibel dengan versi sebelumnya ke WebAssembly yang ada.
  2. Kompiler dasar SpiderMonkey hanyalah satu implementasi, dan mungkin saja itu kurang optimal sehubungan dengan alokasi register: bahwa jika itu sedikit lebih pintar, manfaat runtime akan lebih besar daripada biaya waktu kompilasi.
  3. Bahkan jika kompiler dasar tidak memerlukan informasi tambahan, kompiler yang dioptimalkan mungkin memerlukannya untuk konstruksi SSA yang cepat.

Dengan mengingat hal tersebut, eksperimen pemikiran di atas memperkuat keyakinan saya bahwa penyusun dasar tidak memerlukan aliran kontrol terstruktur . Terlepas dari seberapa rendah konstruksi yang kita tambahkan, selama itu mencakup informasi dasar seperti instruksi mana yang menjadi target lompatan, penyusun dasar dapat menanganinya hanya dengan perubahan kecil. Atau setidaknya yang ini bisa.

@conrad-watt @comex

Itu poin yang sangat bagus! Intuisi saya tentang kompiler dasar mungkin salah.

Dan @comex - ya, seperti yang Anda katakan, diskusi ini terpisah dari pengoptimalan kompiler di mana SSA dapat mengambil manfaat dari strukturnya. Mungkin layak dikutip sedikit dari salah satu tautan sebelumnya :

Secara desain, mengubah kode WebAssembly menjadi IR TurboFan (termasuk konstruksi SSA) dalam satu lintasan sangat efisien, sebagian karena aliran kontrol terstruktur WebAssembly.

@conrad-watt Saya sangat setuju kita hanya perlu mendapatkan umpan balik langsung dari orang-orang VM, dan tetap berpikiran terbuka. Untuk lebih jelasnya, tujuan saya di sini bukan untuk menghentikan apa pun. Saya berkomentar panjang lebar di sini karena beberapa komentar tampaknya berpikir bahwa aliran kontrol terstruktur wasm adalah kesalahan yang jelas atau yang jelas harus diperbaiki dengan funclets/multiloop - saya hanya ingin menyajikan sejarah pemikiran di sini, dan ada alasan kuat untuk model saat ini, jadi mungkin tidak mudah untuk memperbaikinya.

Saya sangat senang membaca percakapan ini. Saya sendiri bertanya-tanya banyak pertanyaan ini (datang dari kedua arah), dan berbagi banyak pemikiran ini (sekali lagi dari kedua arah), dan diskusi telah menawarkan banyak wawasan dan pengalaman yang berguna. Saya tidak yakin saya memiliki pendapat yang kuat, tetapi saya memiliki pemikiran untuk berkontribusi di setiap arah.

Di sisi "untuk", penting untuk mengetahui di depan blok mana yang memiliki tepi belakang. Kompiler streaming dapat melacak properti yang tidak terlihat dalam sistem tipe WebAssembly (misalnya indeks di i berada dalam batas array di lokal arr ). Saat melompat ke depan, akan berguna untuk memberi anotasi pada target dengan properti apa yang dipegang pada saat itu. Dengan begitu ketika sebuah label tercapai, bloknya dapat dikompilasi menggunakan properti yang menahan semua sisi dalam, katakanlah untuk menghilangkan pemeriksaan batas-array. Tetapi jika label berpotensi memiliki backedge yang tidak diketahui, maka bloknya tidak dapat dikompilasi dengan pengetahuan ini. Tentu saja, kompiler non-streaming dapat melakukan beberapa analisis invarian loop yang lebih signifikan, tetapi untuk kompiler streaming, ada baiknya tidak perlu khawatir tentang apa yang mungkin terjadi di masa depan. (Pikiran sampingan: @Vurich menyebutkan bahwa WebAssembly bukan mesin tumpukan karena penggunaan penduduk setempat. Di #1381 saya memaparkan beberapa alasan untuk kurang mengandalkan penduduk setempat dan menambahkan lebih banyak operasi tumpukan. Membuat alokasi register lebih mudah tampaknya menjadi alasan lain dalam arah itu.)

Di sisi "melawan", selama ini pembahasan hanya terfokus pada kontrol lokal. Itu bagus untuk C, tetapi bagaimana dengan C++ atau berbagai bahasa lain dengan pengecualian serupa? Bagaimana dengan bahasa dengan bentuk lain dari kontrol non-lokal? Hal-hal dengan ruang lingkup dinamis sering kali secara inheren terstruktur (atau setidaknya saya tidak tahu contoh ruang lingkup dinamis yang saling rekursif). Saya pikir pertimbangan ini dapat diatasi, tetapi Anda harus merancang sesuatu dengan mempertimbangkannya agar hasilnya dapat digunakan dalam pengaturan ini. Ini adalah sesuatu yang telah saya renungkan, dan saya senang untuk membagikan pemikiran saya yang sedang berjalan (tampak kira-kira seperti perpanjangan dari multi-loop @ conrad-watt) dengan siapa saja yang tertarik (walaupun di sini tampaknya di luar topik), tetapi Saya ingin setidaknya memberi tahu bahwa ada lebih dari sekadar aliran kontrol lokal yang perlu diingat.

(Saya juga ingin memberikan +1 lain untuk mendengar lebih banyak dari orang-orang VM, meskipun saya pikir @kripken telah melakukan pekerjaan yang baik untuk mewakili pertimbangan.)

Ketika saya mengatakan Lightbeam menghasilkan IR internal, itu benar-benar sangat menyesatkan dan saya seharusnya mengklarifikasi. Saya sedang mengerjakan proyek untuk sementara waktu dan terkadang Anda bisa mendapatkan visi terowongan. Pada dasarnya, Lightbeam mengkonsumsi instruksi input dengan instruksi (sebenarnya memiliki maksimum satu instruksi lookahead tapi itu tidak terlalu penting), dan untuk setiap instruksi yang dihasilkannya, dengan malas dan dalam ruang konstan, sejumlah instruksi IR internal. Jumlah maksimum instruksi per instruksi Wasm adalah konstan dan kecil, seperti 6. Ini tidak membuat buffer instruksi IR untuk seluruh fungsi dan mengerjakannya. Kemudian, ia membaca instruksi IR tersebut satu per satu. Anda benar-benar dapat menganggapnya sebagai memiliki perpustakaan fungsi pembantu yang lebih umum yang mengimplementasikan setiap instruksi Wasm dalam hal, saya hanya menyebutnya sebagai IR karena itu membantu menjelaskan bagaimana ia memiliki model yang berbeda untuk aliran kontrol dll. Ini mungkin tidak menghasilkan kode secepat kompiler dasar V8 atau SpiderMonkey, tetapi itu karena tidak sepenuhnya dioptimalkan dan bukan karena kekurangan arsitektur. Maksud saya adalah bahwa saya secara internal memodelkan aliran kontrol hierarkis Wasm seolah-olah itu adalah CFG, daripada benar-benar menghasilkan buffer IR dalam memori seperti yang dilakukan LLVM atau Cranelift.

Pilihan lain adalah mengkompilasi wasm ke sesuatu yang Anda yakini dapat menangani aliran kontrol secara optimal, yaitu, "membatalkan" penataan. LLVM seharusnya bisa melakukan itu, jadi menjalankan wasm di VM yang menggunakan LLVM (seperti WAVM atau wasmer) atau melalui WasmBoxC bisa jadi menarik.

@kripken Sayangnya, LLVM tampaknya belum dapat membatalkan penataan. Pass optimasi jump threading seharusnya dapat melakukan ini, tetapi belum mengenali pola ini. Berikut adalah contoh yang menunjukkan beberapa kode C++ yang meniru bagaimana algoritma relooper akan mengubah CFG menjadi loop+switch. GCC berhasil "dereloop", tetapi dentang tidak: https://godbolt.org/z/GGM9rP

@AndrewScheidecker Menarik, terima kasih. Ya, hal ini bisa sangat tidak terduga, jadi mungkin tidak ada pilihan yang lebih baik daripada menyelidiki kode yang dipancarkan (seperti yang dilakukan oleh makalah "Tidak Begitu Cepat" yang ditautkan sebelumnya), dan hindari upaya pintasan seperti mengandalkan pengoptimal LLVM.

@comex

Kompiler dasar SpiderMonkey hanyalah satu implementasi, dan mungkin saja itu kurang optimal sehubungan dengan alokasi register: bahwa jika itu sedikit lebih pintar, manfaat runtime akan lebih besar daripada biaya waktu kompilasi.

Jelas bisa lebih pintar tentang alokasi register. Itu tumpah tanpa pandang bulu di garpu aliran kontrol, bergabung, dan sebelum panggilan, dan dapat mempertahankan lebih banyak informasi tentang status register dan mencoba menyimpan nilai dalam register lebih lama / sampai mati. Itu bisa memilih register yang lebih baik daripada rax untuk hasil nilai dari blok, atau lebih baik, tidak menggunakan register tetap. Itu bisa secara statis mendedikasikan beberapa register untuk menampung variabel lokal; analisis korpus yang saya lakukan menyarankan bahwa hanya beberapa bilangan bulat dan register FP akan cukup untuk sebagian besar fungsi. Itu bisa lebih pintar tentang menumpahkan secara umum; sebagaimana adanya, ia menumpahkan semua panik ketika kehabisan register.

Biaya waktu kompilasi ini terutama karena setiap tepi aliran kontrol akan memiliki jumlah informasi yang tidak konstan yang terkait dengannya (status register) dan ini dapat menyebabkan penggunaan alokasi penyimpanan dinamis yang lebih luas, yang dimiliki oleh penyusun dasar. jauh dihindari. Dan tentu saja akan ada biaya yang terkait dengan pemrosesan informasi ukuran variabel itu di setiap gabungan (dan tempat lainnya). Tetapi sudah ada beberapa biaya yang tidak konstan karena status register harus dilalui untuk menghasilkan kode tumpahan, dan pada umumnya mungkin ada beberapa nilai yang aktif, jadi ini mungkin OK (atau tidak). Tentu saja, menjadi lebih pintar dengan regalloc mungkin atau mungkin tidak terbayar pada chip modern, dengan cache cepat dan eksekusi ooo ...

Biaya yang lebih halus adalah pemeliharaan kompiler... itu sudah cukup kompleks, dan karena ini adalah satu-pass dan tidak membangun grafik IR atau menggunakan memori dinamis sama sekali, tahan terhadap layering dan abstraksi.

@RossTate

Re funclets / gotos, saya membaca sekilas spesifikasi funclet beberapa hari yang lalu dan pada pandangan pertama itu tidak terlihat seperti kompiler satu-pass seharusnya memiliki masalah nyata dengannya, tentu saja tidak dengan skema regalloc yang sederhana. Tetapi bahkan dengan skema yang lebih baik itu mungkin baik-baik saja: Tepi pertama yang mencapai titik gabungan akan memutuskan apa tugas registernya, dan tepi lainnya harus menyesuaikan.

@conrad-watt seperti yang baru saja Anda sebutkan dalam pertemuan CG, saya pikir kami akan sangat tertarik untuk melihat detail tentang seperti apa tampilan multi-loop Anda.

@aardappel ya, hidup telah datang pada saya dengan cepat, tetapi saya harus melakukan ini di pertemuan berikutnya. Hanya untuk menekankan bahwa ide itu bukan milik saya karena @rossberg awalnya membuat sketsa sebagai tanggapan terhadap draf pertama funclet.

Satu referensi yang mungkin instruktif adalah semacam agak ketinggalan zaman, tetapi menggeneralisasi pengertian yang sudah dikenal tentang loop untuk menangani loop yang tidak dapat direduksi menggunakan grafik DJ .

Kami telah melakukan beberapa sesi diskusi tentang ini di CG, dan saya telah menulis ringkasan dan dokumen tindak lanjut. Karena panjangnya saya membuatnya menjadi inti yang terpisah.

https://Gist.github.com/conrad-watt/6a620cb8b7d8f0191296e3eb24dffdef

Saya pikir dua pertanyaan langsung yang dapat ditindaklanjuti (lihat bagian tindak lanjut untuk detail lebih lanjut) adalah:

  • Bisakah kita menemukan program "liar" yang saat ini menderita dan akan mendapat manfaat dari segi kinerja dari multiloop ? Ini mungkin program yang transformasi LLVM memperkenalkan aliran kontrol yang tidak dapat direduksi bahkan jika tidak ada di program sumber.
  • Apakah ada dunia di mana multiloop diimplementasikan di sisi produsen terlebih dahulu, dengan beberapa lapisan penerapan tautan/terjemahan untuk Wasm "Web"?

Mungkin juga ada lebih banyak diskusi bebas tentang konsekuensi dari masalah penanganan pengecualian yang saya diskusikan dalam dokumen tindak lanjut, dan tentu saja pelepasan sepeda standar tentang detail semantik jika kita bergerak maju dengan sesuatu yang konkret.

Karena diskusi ini mungkin agak bercabang, mungkin tepat untuk memutar beberapa di antaranya menjadi masalah di repositori funclets .

Saya sangat senang melihat kemajuan dalam masalah ini. Besar "Terima kasih" untuk semua orang yang terlibat!

Bisakah kita menemukan program "liar" yang saat ini menderita dan akan mendapat manfaat dari segi kinerja dari multiloop? Ini mungkin program yang transformasi LLVM memperkenalkan aliran kontrol yang tidak dapat direduksi bahkan jika tidak ada di program sumber.

Saya ingin sedikit berhati-hati terhadap penalaran melingkar: Program yang saat ini memiliki kinerja buruk cenderung tidak terjadi "di alam liar" karena alasan ini.

Saya pikir sebagian besar program Go seharusnya mendapat banyak manfaat. Compiler Go membutuhkan coroutine WebAssembly atau multiloop untuk dapat memancarkan kode efisien yang mendukung goroutine Go.

Pencocokan ekspresi reguler yang telah dikompilasi sebelumnya, bersama dengan mesin status yang telah dikompilasi sebelumnya, sering kali menghasilkan aliran kontrol yang tidak dapat direduksi. Sulit untuk mengatakan apakah algoritma "fusi" untuk Jenis Antarmuka akan menghasilkan aliran kontrol yang tidak dapat direduksi.

  • Setuju diskusi ini harus dipindahkan ke masalah di funclet (atau baru) repo.
  • Setuju bahwa menemukan program yang akan mendapat manfaat darinya sulit untuk diukur tanpa memiliki LLVM (dan Go, dan lainnya) benar-benar memancarkan aliran kontrol paling optimal (yang mungkin tidak dapat direduksi). Inefisiensi yang disebabkan oleh FixIrreducibleControlFlow dan teman-teman mungkin merupakan masalah "mati dengan seribu luka" di biner besar.
  • Sementara saya akan menyambut implementasi hanya alat sebagai kemajuan minimum absolut yang keluar dari diskusi ini, itu masih belum optimal, karena produsen sekarang memiliki pilihan sulit untuk memanfaatkan fungsi ini untuk kenyamanan (tetapi kemudian menghadapi regresi kinerja yang tidak dapat diprediksi/ tebing), atau melakukan kerja keras untuk memperdebatkan output mereka ke standar wasm agar hal-hal dapat diprediksi.
  • Jika diputuskan bahwa "gotos" paling baik merupakan fitur alat saja, saya berpendapat bahwa Anda mungkin bisa lolos dengan fitur yang lebih sederhana daripada multiloop, karena yang Anda pedulikan hanyalah kenyamanan produsen. Minimal, goto <function_byte_offset> akan menjadi satu-satunya hal yang perlu dimasukkan ke dalam badan fungsi Wasm biasa untuk memungkinkan WABT atau Binaryen mengubahnya menjadi Wasm legal. Hal-hal seperti tanda tangan tipe berguna jika mesin perlu memverifikasi multiloop dengan cepat, tetapi jika itu adalah alat yang mudah digunakan, mungkin juga membuatnya nyaman untuk dipancarkan.

Setuju bahwa menemukan program yang akan mendapat manfaat darinya sulit untuk diukur tanpa memiliki LLVM (dan Go, dan lainnya) benar-benar memancarkan aliran kontrol paling optimal (yang mungkin tidak dapat direduksi).

Saya setuju bahwa pengujian pada rantai alat + VM yang dimodifikasi akan optimal. Namun kita dapat membandingkan build wasm saat ini dengan build asli yang memiliki aliran kontrol optimal. Not So Fast dan lainnya telah melihat hal ini dengan berbagai cara (penghitung kinerja, penyelidikan langsung) dan tidak menemukan aliran kontrol yang tidak dapat direduksi sebagai faktor yang signifikan.

Lebih khusus lagi, mereka tidak menganggapnya sebagai faktor signifikan untuk C/C++. Itu mungkin lebih berkaitan dengan C/C++ daripada dengan kinerja aliran kontrol yang tidak dapat direduksi. (Sejujurnya saya tidak tahu.) Sepertinya @neelance punya alasan untuk percaya bahwa hal yang sama tidak berlaku untuk Go.

Perasaan saya adalah bahwa ada banyak aspek dari masalah ini, dan ada baiknya menanganinya melalui berbagai arah.

Pertama, sepertinya ada masalah umum dengan kemampuan pembuatan WebAssembly. Sebagian besar disebabkan oleh kendala WebAssembly untuk memiliki biner yang ringkas dengan pemeriksaan tipe dan kompilasi streaming yang efisien. Kami dapat mengatasi masalah ini setidaknya sebagian dengan mengembangkan "pra" -WebAssembly standar yang lebih mudah dibuat tetapi dijamin dapat diterjemahkan ke WebAssembly "benar", idealnya hanya melalui duplikasi kode dan penyisipan instruksi/anotasi yang "dapat dihapus", dengan setidaknya beberapa alat yang menyediakan terjemahan tersebut.

Kedua, kita dapat mempertimbangkan fitur "pra"-WebAssembly apa yang layak untuk langsung digabungkan ke dalam WebAssembly "benar". Kita dapat melakukan ini dengan cara yang terinformasi karena kita akan memiliki modul "pra"-WebAssembly yang dapat kita analisis sebelum diubah menjadi modul WebAssembly "benar".

Beberapa tahun yang lalu saya mencoba mengkompilasi emulator bytecode tertentu untuk bahasa dinamis (https://github.com/ciao-lang/ciao) ke webassembly dan kinerjanya jauh dari optimal (kadang-kadang 10 kali lebih lambat dari versi asli). Loop eksekusi utama berisi sakelar pengiriman bytecode besar, dan mesin disetel dengan baik selama beberapa dekade untuk berjalan pada perangkat keras yang sebenarnya, dan kami banyak menggunakan label dan gotos. Saya ingin tahu apakah perangkat lunak semacam ini akan mendapat manfaat dari dukungan untuk aliran kontrol yang tidak dapat direduksi atau apakah masalahnya adalah masalah lain. Saya tidak punya waktu untuk melakukan penyelidikan lebih lanjut, tetapi saya akan dengan senang hati mencoba lagi jika keadaannya diketahui telah membaik. Tentu saja saya mengerti bahwa mengkompilasi bahasa lain VM ke wasm bukanlah kasus penggunaan utama, tetapi saya sebaiknya mengetahui apakah ini pada akhirnya akan layak, khususnya karena binari universal yang berjalan secara efisien, di mana saja, adalah salah satu keuntungan yang dijanjikan dari wasm. (Terima kasih dan maaf jika topik khusus ini telah dibahas dalam beberapa masalah lain)

@jfmc Pemahaman saya adalah bahwa, jika programnya realistis (yaitu tidak dibuat-buat untuk menjadi patologis) dan Anda peduli dengan kinerjanya, maka itu adalah kasus penggunaan yang benar-benar valid. WebAssembly bertujuan untuk menjadi target tujuan umum yang baik. Jadi saya pikir akan sangat bagus untuk mendapatkan pemahaman tentang mengapa Anda melihat penurunan yang signifikan. Jika itu terjadi karena pembatasan aliran kontrol, maka itu akan sangat berguna untuk diketahui dalam diskusi ini. Jika itu terjadi karena hal lain, maka itu masih berguna untuk mengetahui cara meningkatkan WebAssembly secara umum.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

beriberikix picture beriberikix  ·  7Komentar

JimmyVV picture JimmyVV  ·  4Komentar

aaabbbcccddd00001111 picture aaabbbcccddd00001111  ·  3Komentar

spidoche picture spidoche  ·  4Komentar

thysultan picture thysultan  ·  4Komentar