Apa itu WebAssembly dan dari mana asalnya?

Sejak web menjadi platform tidak hanya untuk dokumen tetapi juga untuk aplikasi, beberapa aplikasi paling canggih telah mendorong browser web hingga batasnya. Pendekatan untuk "lebih dekat dengan standar" dengan berinteraksi dengan bahasa tingkat rendah untuk meningkatkan performa ditemui dalam banyak bahasa tingkat lebih tinggi. Sebagai contoh, Java memiliki Java Native Interface. Untuk JavaScript, bahasa tingkat rendah ini adalah WebAssembly. Dalam artikel ini, Anda akan mengetahui apa itu bahasa assembly, dan mengapa bahasa itu berguna di web, kemudian mempelajari bagaimana WebAssembly dibuat melalui solusi sementara asm.js.

Bahasa assembly

Pernahkah Anda memprogram dalam bahasa assembly? Dalam pemrograman komputer, bahasa assembly, sering disebut sebagai Assembly dan umumnya disingkat sebagai ASM atau asm, adalah semua bahasa pemrograman tingkat rendah dengan hubungan yang sangat kuat antara instruksi dalam bahasa dan instruksi kode mesin arsitektur.

Misalnya, dengan melihat Arsitektur Intel® 64 dan IA-32 (PDF), instruksi MUL (untuk mulsipasi) melakukan perkalian yang tidak ditandatangani dari operand pertama (Operand tujuan) dan operand kedua (Operand sumber), dan menyimpan hasilnya di operand tujuan. Sangat disederhanakan, operand tujuan adalah operand tersirat yang terletak di register AX, dan operand sumber terletak dalam daftar tujuan umum seperti CX. Hasilnya disimpan lagi di pendaftaran AX. Perhatikan contoh kode x86 berikut:

mov ax, 5  ; Set the value of register AX to 5.
mov cx, 10 ; Set the value of register CX to 10.
mul cx     ; Multiply the value of register AX (5)
           ; and the value of register CX (10), and
           ; store the result in register AX.

Sebagai perbandingan, jika ditugaskan dengan tujuan untuk mengalikan 5 dan 10, Anda mungkin akan menulis kode yang mirip dengan berikut ini di JavaScript:

const factor1 = 5;
const factor2 = 10;
const result = factor1 * factor2;

Keuntungan menggunakan rute perakitan adalah kode tingkat rendah dan dioptimalkan mesin tersebut jauh lebih efisien daripada kode tingkat tinggi dan dioptimalkan manusia. Dalam kasus sebelumnya, hal ini tidak menjadi masalah, tetapi Anda dapat membayangkan bahwa untuk operasi yang lebih kompleks, perbedaannya bisa jadi signifikan.

Seperti namanya, kode x86 bergantung pada arsitektur x86. Bagaimana jika ada cara untuk menulis kode assembly yang tidak bergantung pada arsitektur tertentu, tetapi akan mewarisi manfaat performa dari assembly?

asm.js

Langkah pertama untuk menulis kode assembly tanpa dependensi arsitektur adalah asm.js, subset ketat JavaScript yang dapat digunakan sebagai bahasa target tingkat rendah dan efisien untuk compiler. Sub-bahasa ini secara efektif mendeskripsikan mesin virtual dengan sandbox untuk bahasa yang tidak aman untuk memori seperti C atau C++. Kombinasi validasi statis dan dinamis memungkinkan mesin JavaScript menggunakan strategi kompilasi ahead-of-time (AOT) yang mengoptimalkan strategi kompilasi untuk kode asm.js yang valid. Kode yang ditulis dalam bahasa yang diketik secara statis dengan pengelolaan memori manual (seperti C) diterjemahkan oleh compiler sumber-ke-sumber seperti Emscripten awal (berdasarkan LLVM).

Performa ditingkatkan dengan membatasi fitur bahasa hanya pada fitur yang dapat menerima AOT. Firefox 22 adalah browser pertama yang mendukung asm.js, yang dirilis dengan nama OdinMonkey. Chrome menambahkan dukungan asm.js di versi 61. Meskipun asm.js masih berfungsi di browser, asm.js telah digantikan oleh WebAssembly. Alasan untuk menggunakan asm.js saat ini adalah sebagai alternatif untuk browser yang tidak memiliki dukungan WebAssembly.

WebAssembly

WebAssembly adalah bahasa tingkat rendah seperti assembly dengan format biner ringkas yang berjalan dengan performa yang mendekati native dan menyediakan bahasa seperti C/C++ dan Rust, serta banyak lagi dengan target kompilasi sehingga dapat berjalan di web. Dukungan untuk bahasa yang dikelola memori seperti Java, dan Dart sedang dalam proses dan akan segera tersedia, atau sudah tersedia seperti pada kasus Kotlin/Wasm. WebAssembly dirancang untuk berjalan bersama JavaScript, sehingga memungkinkan keduanya bekerja sama.

Selain browser, program WebAssembly juga berjalan di runtime lainnya berkat WASI, Antarmuka Sistem WebAssembly, antarmuka sistem modular untuk WebAssembly. WASI dibuat agar portabel di seluruh sistem operasi, dengan tujuan aman dan dapat berjalan di lingkungan dalam sandbox.

Kode WebAssembly (kode biner, yaitu bytecode) ditujukan untuk dijalankan pada mesin stack virtual (VM) portabel. Bytecode didesain agar lebih cepat diurai dan dieksekusi daripada JavaScript dan untuk memiliki representasi kode yang ringkas.

Eksekusi konseptual instruksi dilakukan melalui penghitung program tradisional yang melanjutkan petunjuk. Dalam praktiknya, sebagian besar mesin Wasm mengompilasi bytecode Wasm ke kode mesin, lalu menjalankannya. Petunjuk dibagi ke dalam dua kategori:

  • Petunjuk kontrol yang membentuk konstruksi kontrol dan memunculkan nilai argumennya dari stack, dapat mengubah penghitung program, dan mendorong nilai hasil ke stack.
  • Petunjuk sederhana yang memunculkan nilai argumen dari stack, menerapkan operator ke nilai, lalu mengirim nilai hasil ke stack, diikuti dengan kemajuan implisit dari penghitung program.

Kembali ke contoh sebelumnya, kode WebAssembly berikut akan setara dengan kode x86 dari awal artikel:

i32.const 5  ; Push the integer value 5 onto the stack.
i32.const 10 ; Push the integer value 10 onto the stack.
i32.mul      ; Pop the two most recent items on the stack,
             ; multiply them, and push the result onto the stack.

Meskipun asm.js diterapkan di semua software, artinya kodenya dapat berjalan di semua mesin JavaScript (meskipun tidak dioptimalkan), WebAssembly memerlukan fungsi baru yang disetujui oleh semua vendor browser. Diumumkan pada tahun 2015 dan pertama kali dirilis pada Maret 2017, WebAssembly menjadi rekomendasi W3C pada 5 Desember 2019. W3C mempertahankan standar tersebut dengan kontribusi dari semua vendor browser utama dan pihak-pihak yang berkepentingan lainnya. Sejak 2017, dukungan browser bersifat universal.

WebAssembly memiliki dua representasi: texttual dan biner. Apa yang Anda lihat di atas adalah representasi tekstual.

Representasi tekstual

Representasi tekstual didasarkan pada S-expressions dan biasanya menggunakan ekstensi file .wat (untuk format WebAssembly text). Jika Anda benar-benar ingin menulisnya, Anda bisa menulisnya dengan tangan. Dengan mengambil contoh perkalian di atas dan membuatnya lebih berguna dengan tidak lagi melakukan hardcode pada faktor-faktor, Anda mungkin dapat memahami kode berikut:

(module
  (func $mul (param $factor1 i32) (param $factor2 i32) (result i32)
    local.get $factor1
    local.get $factor2
    i32.mul)
  (export "mul" (func $mul))
)

Representasi biner

Format biner yang menggunakan ekstensi file .wasm tidak dimaksudkan untuk digunakan oleh manusia, apalagi buatan manusia. Dengan alat seperti wat2wasm, Anda dapat mengonversi kode di atas ke representasi biner berikut. (Komentar biasanya bukan bagian dari representasi biner, tetapi ditambahkan oleh alat wat2wasm untuk lebih mudah dipahami.)

0000000: 0061 736d                             ; WASM_BINARY_MAGIC
0000004: 0100 0000                             ; WASM_BINARY_VERSION
; section "Type" (1)
0000008: 01                                    ; section code
0000009: 00                                    ; section size (guess)
000000a: 01                                    ; num types
; func type 0
000000b: 60                                    ; func
000000c: 02                                    ; num params
000000d: 7f                                    ; i32
000000e: 7f                                    ; i32
000000f: 01                                    ; num results
0000010: 7f                                    ; i32
0000009: 07                                    ; FIXUP section size
; section "Function" (3)
0000011: 03                                    ; section code
0000012: 00                                    ; section size (guess)
0000013: 01                                    ; num functions
0000014: 00                                    ; function 0 signature index
0000012: 02                                    ; FIXUP section size
; section "Export" (7)
0000015: 07                                    ; section code
0000016: 00                                    ; section size (guess)
0000017: 01                                    ; num exports
0000018: 03                                    ; string length
0000019: 6d75 6c                          mul  ; export name
000001c: 00                                    ; export kind
000001d: 00                                    ; export func index
0000016: 07                                    ; FIXUP section size
; section "Code" (10)
000001e: 0a                                    ; section code
000001f: 00                                    ; section size (guess)
0000020: 01                                    ; num functions
; function body 0
0000021: 00                                    ; func body size (guess)
0000022: 00                                    ; local decl count
0000023: 20                                    ; local.get
0000024: 00                                    ; local index
0000025: 20                                    ; local.get
0000026: 01                                    ; local index
0000027: 6c                                    ; i32.mul
0000028: 0b                                    ; end
0000021: 07                                    ; FIXUP func body size
000001f: 09                                    ; FIXUP section size
; section "name"
0000029: 00                                    ; section code
000002a: 00                                    ; section size (guess)
000002b: 04                                    ; string length
000002c: 6e61 6d65                       name  ; custom section name
0000030: 01                                    ; name subsection type
0000031: 00                                    ; subsection size (guess)
0000032: 01                                    ; num names
0000033: 00                                    ; elem index
0000034: 03                                    ; string length
0000035: 6d75 6c                          mul  ; elem name 0
0000031: 06                                    ; FIXUP subsection size
0000038: 02                                    ; local name type
0000039: 00                                    ; subsection size (guess)
000003a: 01                                    ; num functions
000003b: 00                                    ; function index
000003c: 02                                    ; num locals
000003d: 00                                    ; local index
000003e: 07                                    ; string length
000003f: 6661 6374 6f72 31            factor1  ; local name 0
0000046: 01                                    ; local index
0000047: 07                                    ; string length
0000048: 6661 6374 6f72 32            factor2  ; local name 1
0000039: 15                                    ; FIXUP subsection size
000002a: 24                                    ; FIXUP section size

Mengompilasi ke WebAssembly

Seperti yang Anda lihat, baik .wat maupun .wasm tidak terlalu ramah manusia. Di sinilah compiler seperti Emscripten berperan. Alat ini memungkinkan Anda mengompilasi dari bahasa tingkat tinggi seperti C dan C++. Ada kompilator lain untuk bahasa lain seperti Rust dan banyak lagi. Pertimbangkan kode C berikut:

#include <stdio.h>

int main() {
  printf("Hello World\n");
  return 0;
}

Biasanya, Anda akan mengompilasi program C ini dengan compiler gcc.

$ gcc hello.c -o hello

Setelah Emscripten diinstal, Anda dapat mengompilasinya ke WebAssembly menggunakan perintah emcc dan argumen yang hampir sama:

$ emcc hello.c -o hello.html

Tindakan ini akan membuat file hello.wasm dan file wrapper HTML hello.html. Saat menyalurkan file hello.html dari server web, Anda akan melihat "Hello World" yang dicetak ke konsol DevTools.

Ada juga cara untuk mengompilasi ke WebAssembly tanpa wrapper HTML:

$ emcc hello.c -o hello.js

Seperti sebelumnya, tindakan ini akan membuat file hello.wasm, tetapi kali ini file hello.js, bukan wrapper HTML. Untuk menguji, jalankan file JavaScript yang dihasilkan hello.js dengan, misalnya, Node.js:

$ node hello.js
Hello World

Pelajari lebih lanjut

Pengantar singkat tentang WebAssembly ini hanyalah sebagian kecil dari gunung es. Pelajari WebAssembly lebih lanjut dalam dokumentasi WebAssembly di MDN dan lihat dokumentasi Emscripten. Sejujurnya, bekerja dengan WebAssembly bisa terasa seperti Cara menggambar meme burung hantu, terutama karena developer web yang memahami HTML, CSS, dan JavaScript belum tentu ahli dalam bahasa yang akan dikompilasi dari bahasa C. Untungnya, ada saluran seperti tag webassembly StackOverflow di mana para pakar sering kali akan membantu dengan senang hati jika Anda meminta dengan baik.

Ucapan terima kasih

Artikel ini ditinjau oleh Jakob Kummerow, Derek Schuff, dan Rachel Andrew.