นับตั้งแต่ที่เว็บกลายเป็นแพลตฟอร์มที่ไม่ได้มีไว้สำหรับเอกสารเท่านั้น แต่ยังมีแอปด้วย แอปที่ล้ำสมัยที่สุดบางแอปได้ทดสอบขีดจำกัดของเว็บเบราว์เซอร์ แนวทาง "เข้าใกล้ระดับล่าง" โดยการติดต่อกับภาษาระดับล่างเพื่อปรับปรุงประสิทธิภาพนั้นพบได้ในภาษาระดับสูงหลายภาษา ตัวอย่างเช่น Java มี Java Native Interface สำหรับ JavaScript ภาษาระดับล่างนี้คือ WebAssembly บทความนี้จะอธิบายว่าภาษาแอสเซมบลีคืออะไร และเหตุใดจึงมีประโยชน์บนเว็บ จากนั้นจะอธิบายวิธีสร้าง WebAssembly ผ่านโซลูชันชั่วคราวของ asm.js
ภาษาแอสเซมบลี
คุณเคยเขียนโปรแกรมด้วยภาษาแอสเซมบลีไหม ในภาษาโปรแกรมคอมพิวเตอร์ ภาษาแอสเซมบลีซึ่งมักเรียกสั้นๆ ว่าแอสเซมบลีและมักย่อเป็น ASM หรือ asm เป็นภาษาโปรแกรมระดับต่ำใดๆ ที่มีการเชื่อมโยงอย่างแน่นหนาระหว่างคำสั่งในภาษากับคำสั่งโค้ดเครื่องของสถาปัตยกรรม
ตัวอย่างเช่น เมื่อดูที่สถาปัตยกรรม Intel® 64 และ IA-32 (PDF) คำสั่ง MUL
(สําหรับ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
ขั้นตอนแรกในการเขียนโค้ดแอสเซมบลีที่ไม่ขึ้นอยู่กับสถาปัตยกรรมคือ 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 เป็นภาษาระดับต่ำที่คล้ายกับ Assembly โดยมีรูปแบบไบนารีที่กะทัดรัดซึ่งทำงานได้อย่างมีประสิทธิภาพใกล้เคียงกับแบบเนทีฟ และมีภาษาต่างๆ เช่น C/C++ และ Rust รวมถึงภาษาอื่นๆ อีกมากมายที่มีเป้าหมายการคอมไพล์เพื่อให้ทำงานบนเว็บได้ เรากำลังดำเนินการรองรับภาษาที่มีการจัดการหน่วยความจำ เช่น Java และ Dart และควรพร้อมใช้งานในเร็วๆ นี้ หรือพร้อมใช้งานแล้วในบางกรณี เช่น Kotlin/Wasm WebAssembly ออกแบบมาให้ทำงานร่วมกับ JavaScript ซึ่งช่วยให้ทั้ง 2 ภาษาทำงานร่วมกันได้
นอกจากเบราว์เซอร์แล้ว โปรแกรม WebAssembly ยังทำงานได้ในรันไทม์อื่นๆ ได้ด้วย เนื่องด้วย WASI ซึ่งเป็นอินเทอร์เฟซระบบ WebAssembly ซึ่งเป็นอินเทอร์เฟซระบบแบบโมดูลสำหรับ 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 จะใช้ในซอฟต์แวร์ทั้งหมด ซึ่งหมายความว่าโค้ดของ asm.js สามารถทำงานในเครื่องมือ JavaScript ใดก็ได้ (แม้ว่าจะไม่ได้เพิ่มประสิทธิภาพก็ตาม) แต่ WebAssembly จำเป็นต้องใช้ฟังก์ชันการทำงานใหม่ที่ผู้ให้บริการเบราว์เซอร์ทุกรายเห็นพ้องกัน WebAssembly ประกาศในปี 2015 และเปิดตัวครั้งแรกในเดือนมีนาคม 2017 ได้กลายเป็นคําแนะนําของ W3C ในวันที่ 5 ธันวาคม 2019 W3C ดูแลมาตรฐานนี้โดยได้รับความร่วมมือจากผู้ให้บริการเบราว์เซอร์รายใหญ่ทุกรายและบุคคลอื่นๆ ที่สนใจ ตั้งแต่ปี 2017 เป็นต้นไป เราได้รองรับเบราว์เซอร์ทุกประเภท
WebAssembly มีการแสดงผล 2 แบบ ได้แก่ ข้อความและไบนารี สิ่งที่คุณเห็นด้านบนคือการแสดงผลเป็นข้อความ
การนําเสนอที่เป็นข้อความ
การแสดงผลเป็นข้อความจะอิงตาม S-expression และมักใช้นามสกุลไฟล์ .wat
(สำหรับรูปแบบข้อความ WebAssembly 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
และไฟล์ Wrapper HTML 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