WebAssembly คืออะไร?
WebAssembly ถูกกำหนดด้วยคุณสมบัติหลัก 4 ประการ ได้แก่ binary format ขนาดกะทัดรัด, virtual machine แบบ stack-based, การรันแบบ deterministic และ การแยกแบบ sandboxed การทำความเข้าใจแต่ละข้อจะอธิบายว่าทำไม Wasm จึงกลายเป็น universal portable execution layer ที่เป็นอยู่ทุกวันนี้
binary format
หัวข้อที่มีชื่อว่า “binary format”ไฟล์ .wasm คือ binary ที่หนาแน่นและมีการระบุ type ครบถ้วน ต่างจาก JavaScript source ไม่ต้องผ่านการ parse นอกจาก validation pass เดียว runtime สามารถเริ่มคอมไพล์เป็น native machine code ได้เกือบทันที นี่คือเหตุผลที่ Wasm สามารถทำได้ใกล้ native performance แม้ในการโหลดครั้งแรก
binary ยังมีขนาดเล็กอีกด้วย function body, type signature, import และ export ทั้งหมดถูก encode ในรูปแบบ section-based ที่มีขนาดขั้นต่ำ module ที่ทำงานจริงอาจมีน้ำหนักน้อยกว่า 1 kilobyte
โมเดล Stack VM
หัวข้อที่มีชื่อว่า “โมเดล Stack VM”Wasm รันบน stack machine เชิงแนวคิด คำสั่งต่างๆ push ค่าไปที่ implicit operand stack และ pop ออกมา ไม่มี general-purpose register ที่คุณอ้างถึงด้วยชื่อ VM จัดการให้คุณ โมเดลนี้ตรวจสอบได้ง่าย (type checker สามารถ walk stack แบบ static ได้) และคอมไพล์เป็น native code แบบ register-based ที่มีประสิทธิภาพได้ง่าย
คุณจะได้สำรวจสิ่งนี้อย่างลึกซึ้งใน Module 2 เมื่อคุณเขียน WAT ด้วยมือ ตอนนี้ขอสังเกตว่า i32.const 42 push integer 32-bit 42 ลงบน stack และฟังก์ชันที่มี (result i32) คาดว่าจะมี i32 หนึ่งตัวบน stack เมื่อ return
Deterministic Execution
หัวข้อที่มีชื่อว่า “Deterministic Execution”เมื่อได้รับ bytes .wasm เดิมและ input เดิม Wasm module จะให้ ผลลัพธ์เหมือนกันทุกแพลตฟอร์ม ไม่ว่าจะเป็น x86, ARM, RISC-V, browser, server, edge พฤติกรรม floating-point ถูกระบุอย่างแม่นยำ (IEEE 754 โดยไม่มี rounding mode ที่ implementation กำหนดเอง) และไม่มี undefined behaviour ในแบบ C ทำให้ Wasm เหมาะมากสำหรับ reproducible build, cryptography, simulation และทุกโดเมนที่ความแม่นยำระดับ byte มีความสำคัญ
Sandboxed โดยค่าเริ่มต้น
หัวข้อที่มีชื่อว่า “Sandboxed โดยค่าเริ่มต้น”Wasm module ถูก แยกอย่างสมบูรณ์ module ใหม่ไม่มีสิทธิ์เข้าถึง filesystem, network, DOM, clock หรือ OS resource ใดๆ module อยู่ในพื้นที่ linear memory ที่มีเพียงตัว module เองกับ host เท่านั้นที่ address ได้ และ interact กับโลกภายนอกได้เฉพาะผ่านฟังก์ชันที่ host import อย่างชัดเจน เข้ามาเท่านั้น ถ้า host ไม่ provide ฟังก์ชันมา module ก็เรียกใช้ไม่ได้ ไม่มีข้อยกเว้น
สิ่งนี้ทำให้การรัน Wasm ของ third-party ที่ไม่น่าไว้ใจนั้นปลอดภัยในแบบที่การรัน native code โดยอิสระไม่ปลอดภัย
ความสัมพันธ์ .wasm / .wat
หัวข้อที่มีชื่อว่า “ความสัมพันธ์ .wasm / .wat”ไฟล์ .wasm ทุกไฟล์มีรูปแบบข้อความที่เทียบเท่าอย่างแน่นอนที่เรียกว่า WAT (WebAssembly Text format) ทั้งสองรูปแบบเป็น 1:1: .wasm ใดๆ สามารถถอดรหัสเป็น .wat ได้ และ .wat ที่ถูกต้องสามารถ assemble กลับเป็น .wasm ที่เหมือนกันได้ คุณจะเขียน WAT ตลอดทั้งคอร์สนี้ในฐานะวิธีที่ชัดเจนที่สุดในการทำความเข้าใจสิ่งที่ binary มีอยู่จริง
ต่อไปนี้คือ WAT module ที่ง่ายที่สุดเท่าที่จะเป็นไปได้ ฟังก์ชัน export เดียวที่คืนค่า integer 42:
(module (func (export "answer") (result i32) i32.const 42))อ่านจากซ้ายไปขวา: module คือ top-level container; func กำหนดฟังก์ชัน; (export "answer") ทำให้เรียกได้จาก JavaScript ในรูป instance.exports.answer(); (result i32) ประกาศว่า return 32-bit integer หนึ่งตัว; และ i32.const 42 push 42 ลงบน stack ซึ่งกลายเป็น return value
runner ด้านล่างคอมไพล์ WAT string นั้นแบบ runtime โดยใช้ WABT, instantiate module และเรียก answer() คุณควรเห็น answer() = 42 ใน output
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| คอมไพล์ logic เป็น Wasm | ความเร็วใกล้ native, deterministic ข้าม platform, sandbox ปลอดภัยโดยดีฟอลต์ | เพิ่ม bundle size (binary + glue code) และต้องผ่าน fetch/compile/instantiate ก่อนรันได้ |
| เขียนด้วย JavaScript ล้วน | ไม่มี compile step, debug ด้วย browser devtools ที่ mature มาก, source map พร้อมใช้ | performance ขึ้นกับ JIT warm-up ไม่ deterministic เท่า และไม่มี sandbox บังคับระดับ memory |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- เข้าใจว่า binary format ของ Wasm แปลว่าเร็วกว่า JavaScript เสมอ — ในความเป็นจริง hot path ของ JS ที่ผ่าน JIT optimization มาดีแล้วบางครั้งเร็วกว่า Wasm ที่คอมไพล์แบบไม่ optimize
- พยายามย้าย logic ทั้งแอปไปเป็น Wasm module เดียวใหญ่ๆ ทั้งที่ sandbox และ determinism มีประโยชน์ชัดเจนเฉพาะกับ compute-heavy path เท่านั้น ส่วน UI/DOM logic ควรอยู่ใน JS ต่อไป
- ลืมนับ cost ของ fetch + compile + instantiate ตอนออกแบบ architecture — ถ้าเรียก module บ่อยแต่ module เล็กมาก overhead การ instantiate ซ้ำๆ อาจกินประโยชน์จาก binary format ที่กะทัดรัดไปหมด
💡 ตัวอย่างจากของจริง
Figma รีไรท์ C++ rendering engine เดิมให้คอมไพล์เป็น Wasm binary แล้วรันใน sandbox ของ browser ได้โดยตรง ทำให้โหลดไฟล์ design ที่ซับซ้อนเร็วขึ้นมากโดยไม่ต้อง rewrite เป็น JavaScript
Adobe พอร์ต Photoshop desktop codebase (C++) มาเป็น Photoshop บนเว็บ โดยอาศัย Wasm binary format และ deterministic execution เพื่อให้ image processing algorithm เดิมทำงานถูกต้องเหมือนบน desktop