นับตั้งแต่ที่เว็บกลายเป็นแพลตฟอร์ม ไม่ใช่เพียงสำหรับเอกสารเท่านั้น แต่ยังรวมถึงแอปพลิเคชันต่างๆ ด้วย แอปที่ล้ำสมัยที่สุดส่วนหนึ่งได้ผลักดันให้เว็บเบราว์เซอร์มีขีดความสามารถสูงสุด แนวทางในการ "ใกล้เคียงกับโลหะ" ด้วยการสื่อสารกับภาษาระดับล่างเพื่อเพิ่มประสิทธิภาพพบในภาษาระดับสูงหลายๆ ภาษา ตัวอย่างเช่น 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