WebAssembly คืออะไรและมาจากไหน

นับตั้งแต่ที่เว็บกลายเป็นแพลตฟอร์ม ไม่ใช่เพียงสำหรับเอกสารเท่านั้น แต่ยังรวมถึงแอปพลิเคชันต่างๆ ด้วย แอปที่ล้ำสมัยที่สุดส่วนหนึ่งได้ผลักดันให้เว็บเบราว์เซอร์มีขีดความสามารถสูงสุด แนวทางในการ "ใกล้เคียงกับโลหะ" ด้วยการสื่อสารกับภาษาระดับล่างเพื่อเพิ่มประสิทธิภาพพบในภาษาระดับสูงหลายๆ ภาษา ตัวอย่างเช่น Java มี Java Native Interface สำหรับ JavaScript ภาษาระดับล่างนี้คือ WebAssembly ในบทความนี้ คุณจะได้ทราบว่าภาษาแอสเซมบลีคืออะไรและเหตุใดจึงมีประโยชน์บนเว็บ จากนั้นเรียนรู้วิธีการสร้าง WebAssembly ผ่านโซลูชันชั่วคราวของ asm.js

ภาษา Assembly

คุณเคยเขียนโปรแกรมในภาษาต่างๆ ไหม ในการเขียนโปรแกรมคอมพิวเตอร์ ภาษา Assembly หรือที่เรียกกันสั้นๆ ว่า ASM หรือ Asm มักจะเรียกสั้นๆ ว่า ASM หรือ Asm คือภาษาโปรแกรมใดก็ตามที่มีความสอดคล้องอย่างมากระหว่างคำสั่งในภาษาและวิธีการโค้ดเครื่องของสถาปัตยกรรมดังกล่าว

ตัวอย่างเช่น เมื่อดู Intel® 64 และ IA-32 Architectures (PDF) คำสั่ง MUL (สำหรับมัล) จะทำการคูณตัวถูกดำเนินการแรก (ตัวถูกดำเนินการปลายทาง) และโอเปอแรนด์ตัวที่ 2 (ตัวถูกดำเนินการต้นทาง) โดยไม่มีเครื่องหมาย และจัดเก็บผลลัพธ์ในโอเปอแรนด์ปลายทาง พูดง่ายๆ ก็คือ ตัวถูกดำเนินการปลายทางคือตัวถูกดำเนินการโดยนัยที่อยู่ในการลงทะเบียน AX และตัวถูกดำเนินการที่มาจะอยู่ในรีจิสเตอร์วัตถุประสงค์ทั่วไป เช่น CX ผลลัพธ์จะได้รับการจัดเก็บอีกครั้งในการลงทะเบียน AX ลองดูตัวอย่างโค้ด x86 ต่อไปนี้

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.

สำหรับการเปรียบเทียบ หากได้รับหน้าที่โดยมีวัตถุประสงค์ในการคูณ 5 กับ 10 คุณอาจเขียนโค้ดที่คล้ายกับโค้ดต่อไปนี้ใน JavaScript

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

ข้อดีของการใช้เส้นทางประกอบคือโค้ดระดับต่ำที่เพิ่มประสิทธิภาพด้วยคอมพิวเตอร์นั้นมีประสิทธิภาพมากกว่าโค้ดระดับสูงและโค้ดที่เพิ่มประสิทธิภาพโดยมนุษย์ ในกรณีก่อนหน้านี้จะไม่สำคัญ แต่คุณอาจนึกภาพได้ว่าสำหรับการดำเนินการที่ซับซ้อนมากขึ้น ความแตกต่างอาจมีความสำคัญมาก

โค้ด x86 จะขึ้นอยู่กับสถาปัตยกรรม x86 ตามชื่อเรียก จะเป็นอย่างไรหากมีวิธีเขียนโค้ดแอสเซมบลีที่ไม่ได้ใช้สถาปัตยกรรมเฉพาะชนิด แต่จะได้ประโยชน์ด้านประสิทธิภาพจากการประกอบ

asm.js

ขั้นตอนแรกในการเขียนโค้ดแอสเซมบลีที่ไม่มีทรัพยากร Dependency ของสถาปัตยกรรมคือ asm.js ซึ่งเป็นชุดย่อยที่เข้มงวดของ JavaScript ที่สามารถใช้เป็นภาษาเป้าหมายระดับต่ำและมีประสิทธิภาพสำหรับคอมไพเลอร์ ภาษาย่อยนี้อธิบายถึงเครื่องเสมือนที่ผ่านการแซนด์บ็อกซ์อย่างมีประสิทธิภาพสำหรับภาษาที่ไม่ปลอดภัยของหน่วยความจำ เช่น C หรือ C++ การผสมผสานระหว่างการตรวจสอบแบบคงที่และแบบไดนามิกทำให้เครื่องมือ JavaScript สามารถใช้กลยุทธ์การคอมไพล์เพื่อเพิ่มประสิทธิภาพล่วงหน้า (AOT) สำหรับโค้ด asm.js ที่ถูกต้องได้ โค้ดที่เขียนด้วยภาษาที่พิมพ์แบบคงที่พร้อมการจัดการหน่วยความจำด้วยตนเอง (เช่น C) ได้รับการแปลโดยคอมไพเลอร์จากต้นทางถึงแหล่งที่มา เช่น Emscripten ก่อนเปิดตัว (อิงตาม LLVM)

ปรับปรุงประสิทธิภาพได้โดยจํากัดฟีเจอร์ภาษาไว้เฉพาะผู้ที่ยินดีให้ AOT เท่านั้น Firefox 22 เป็นเบราว์เซอร์แรกที่รองรับ asm.js ซึ่งเปิดตัวภายใต้ชื่อ OdinMonkey Chrome เพิ่มการรองรับ asm.js ในเวอร์ชัน 61 ในขณะที่ asm.js ยังคงทำงานได้ในเบราว์เซอร์ แต่ก็มี WebAssembly เข้ามาแทนที่ เหตุผลที่ใช้ asm.js ในตอนนี้จะเป็นอีกทางเลือกหนึ่งสำหรับเบราว์เซอร์ที่ไม่รองรับ WebAssembly

WebAssembly

WebAssembly เป็นภาษาคล้ายแอสเซมบลีระดับต่ำ โดยมีรูปแบบไบนารีแบบกะทัดรัดที่ทำงานได้อย่างมีประสิทธิภาพใกล้เคียงกับภาษาเดิมและให้บริการภาษาต่างๆ เช่น C/C++ และ Rust และอื่นๆ อีกมากมายที่มีเป้าหมายการรวบรวมเพื่อให้แสดงบนเว็บได้ การรองรับภาษาที่จัดการด้วยหน่วยความจำ เช่น Java และ Dart อยู่ระหว่างดำเนินการและน่าจะพร้อมให้บริการในเร็วๆ นี้ หรือมีให้บริการใน Kotlin/Wasm แล้ว WebAssembly ออกแบบมาให้ทำงานควบคู่ไปกับ JavaScript ทำให้ทั้ง 2 อย่างสามารถทำงานร่วมกันได้

นอกเหนือจากเบราว์เซอร์แล้ว โปรแกรม WebAssembly ยังทำงานในรันไทม์อื่นๆ ได้ด้วย WASI, WebAssembly System Interface ซึ่งเป็นอินเทอร์เฟซระบบแบบโมดูลสำหรับ WebAssembly WASI สร้างขึ้นเพื่อให้พกพาได้ในระบบปฏิบัติการต่างๆ โดยมีวัตถุประสงค์เพื่อความปลอดภัยและความสามารถในการใช้งานในสภาพแวดล้อมแบบแซนด์บ็อกซ์

โค้ด WebAssembly (โค้ดไบนารีหรือไบต์โค้ด) มีไว้เพื่อเรียกใช้ในเครื่องเสมือน (VM) แบบพกพา ไบต์โค้ดนี้ออกแบบมาให้แยกวิเคราะห์และดำเนินการได้รวดเร็วกว่า JavaScript และมีการนำเสนอโค้ดแบบกะทัดรัด

การดำเนินการในเชิงแนวคิดจะดำเนินไปโดยการใช้ตัวนับโปรแกรมแบบดั้งเดิมที่ดำเนินขั้นตอนต่อๆ ไป ในทางปฏิบัติ เครื่องมือ Wasm ส่วนใหญ่จะคอมไพล์ไบต์โค้ด Wasm เป็นโค้ดของเครื่องแล้วจึงเรียกใช้งาน วิธีการแบ่งออกเป็น 2 หมวดหมู่ ดังนี้

  • คำสั่งควบคุมที่การควบคุมแบบฟอร์มสร้างและเรียกค่าอาร์กิวเมนต์ออกจากกองอาจเปลี่ยนตัวนับโปรแกรม และพุชค่าผลลัพธ์ไปยังกองซ้อนได้
  • วิธีการง่ายๆ ที่แสดงค่าอาร์กิวเมนต์จากชุดรายการ ใช้โอเปอเรเตอร์กับค่า จากนั้นพุชค่าผลลัพธ์ไปยังกองซ้อน ตามด้วยความก้าวหน้าโดยนัยของตัวนับโปรแกรม

กลับไปที่ตัวอย่างก่อนหน้านี้ โค้ด WebAssembly ต่อไปนี้จะมีค่าเท่ากับโค้ด x86 ในช่วงต้นของบทความ

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.

แม้ว่า asm.js จะติดตั้งอยู่ในซอฟต์แวร์ทั้งหมด กล่าวคือ โค้ดของมันสามารถทำงานในเครื่องมือ JavaScript ใดก็ได้ (แม้จะไม่ได้เพิ่มประสิทธิภาพ) WebAssembly ต้องใช้ฟังก์ชันใหม่ที่ผู้ให้บริการเบราว์เซอร์ทั้งหมดตกลงกันไว้ ประกาศในปี 2015 และเผยแพร่ครั้งแรกในเดือนมีนาคม 2017 ว่า WebAssembly กลายเป็นคำแนะนำจาก W3C เมื่อวันที่ 5 ธันวาคม 2019 W3C รักษามาตรฐานจากการมีส่วนร่วมจากผู้ให้บริการเบราว์เซอร์รายใหญ่ทั้งหมดและผู้ที่มีความสนใจ ตั้งแต่ปี 2017 เป็นต้นมา การรองรับเบราว์เซอร์เป็นสากล

WebAssembly มีการแสดงผล 2 แบบ ได้แก่ แบบข้อความและไบนารี สิ่งที่คุณเห็นด้านบนคือการนำเสนอข้อความ

การนำเสนอแบบข้อความ

การนำเสนอข้อความจะขึ้นอยู่กับ S-expressions และมักจะใช้นามสกุลไฟล์ .wat (สำหรับรูปแบบ TebAsembly t) คุณสามารถใช้ลายมือเขียนได้หากคุณต้องการ นำตัวอย่างการคูณจากด้านบนมาทำให้มีประโยชน์มากขึ้นโดยไม่ต้องฮาร์ดโค้ดตัวประกอบอีกต่อไป คุณอาจทำความเข้าใจรหัสต่อไปนี้ได้

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

การแสดงค่าไบนารี

รูปแบบไบนารีที่ใช้นามสกุลไฟล์ .wasm ไม่ได้มีไว้สําหรับการบริโภคของมนุษย์ แต่ต่างจากสิ่งที่มนุษย์สร้างขึ้น การใช้เครื่องมือ เช่น wat2wasm จะสามารถแปลงโค้ดข้างต้นเป็นค่าฐานสองต่อไปนี้ได้ (ความคิดเห็นมักไม่ได้เป็นส่วนหนึ่งของการแสดงไบนารี แต่เพิ่มโดยเครื่องมือ wat2wasm เพื่อให้เข้าใจได้ดียิ่งขึ้น)

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

กำลังคอมไพล์ไปยัง WebAssembly

คุณจะเห็นได้ว่าทั้ง .wat และ .wasm ไม่ได้เป็นมิตรกับมนุษย์อย่างมาก และนี่คือจุดที่คอมไพเลอร์อย่าง Emscripten จะเข้ามามีบทบาท เครื่องมือนี้ช่วยให้คุณคอมไพล์จากภาษาระดับสูงกว่า C และ C++ นอกจากนี้ยังมีคอมไพเลอร์อื่นสำหรับภาษาอื่นๆ อย่าง Rust และอื่นๆ อีกมากมาย ลองดูโค้ด C ต่อไปนี้

#include <stdio.h>

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

โดยปกติแล้ว คุณจะคอมไพล์โปรแกรม C นี้ด้วยคอมไพเลอร์ gcc

$ gcc hello.c -o hello

เมื่อติดตั้ง Emscripten แล้ว คุณจะคอมไพล์ไปยัง WebAssembly ได้โดยใช้คำสั่ง emcc และอาร์กิวเมนต์แทบจะเหมือนกันดังนี้

$ emcc hello.c -o hello.html

การดำเนินการนี้จะสร้างไฟล์ hello.wasm และไฟล์ HTML Wrapper hello.html เมื่อแสดงไฟล์ hello.html จากเว็บเซิร์ฟเวอร์ คุณจะเห็น "Hello World" ที่พิมพ์ไปยังคอนโซลเครื่องมือสำหรับนักพัฒนาเว็บ

นอกจากนี้ วิธีคอมไพล์ไปยัง WebAssembly โดยไม่ใช้ Wrapper ของ HTML ทำดังต่อไปนี้ด้วย

$ emcc hello.c -o hello.js

การดำเนินการนี้จะสร้างไฟล์ hello.wasm อย่างเช่นก่อนหน้านี้ แต่คราวนี้เป็นไฟล์ hello.js แทนที่จะเป็น Wrapper HTML หากต้องการทดสอบ คุณเรียกใช้ไฟล์ JavaScript ที่ได้ hello.js กับ Node.js เช่น

$ node hello.js
Hello World

ดูข้อมูลเพิ่มเติม

ข้อมูลเบื้องต้นคร่าวๆ เกี่ยวกับ WebAssembly นี้เป็นเพียงเคล็ดลับเล็กๆ น้อยๆ เท่านั้น ดูข้อมูลเพิ่มเติมเกี่ยวกับ WebAssembly ในเอกสารประกอบของ WebAssembly เกี่ยวกับ MDN และดูเอกสารประกอบของ Emscripten ความจริงแล้ว การทำงานกับ WebAssembly อาจคล้ายกับวิธีวาดมีมนกฮูก โดยเฉพาะอย่างยิ่ง เนื่องจากนักพัฒนาเว็บที่คุ้นเคยกับ HTML, CSS และ JavaScript อาจไม่ได้เชี่ยวชาญเกี่ยวกับภาษาที่จะนำมาคอมไพล์จากภาษาต่างๆ อย่างเช่น C โชคดีที่มีช่องอย่างแท็ก webassembly ของ StackOverflow ที่ผู้เชี่ยวชาญยินดีให้ความช่วยเหลือเสมอหากคุณถามอย่างดีๆ

ข้อความแสดงการยอมรับ

บทความนี้ได้รับการตรวจสอบโดย Jakob Kummerow, Derek Schuff และ Rachel Andrew