ข้ามไปยังเนื้อหา

Wasm Module แรกของคุณ

ทุกการเดินทาง Wasm เริ่มต้นด้วย loop เดียวกัน: เขียน module ใน WebAssembly Text format (WAT), คอมไพล์ เป็น bytes, instantiate และ เรียก ฟังก์ชันที่ export บทเรียนนี้พาคุณเดิน loop นั้นตั้งแต่ต้นจนจบด้วยตัวอย่างที่เล็กที่สุดเท่าที่จะเป็นไปได้ ฟังก์ชัน double ที่คูณ argument ด้วยสอง

เมื่อจบบทเรียนนี้ คุณจะได้เขียน WAT จริง, คอมไพล์ live ใน browser และเรียกใช้จาก JavaScript

WAT คือการแสดงข้อความที่มนุษย์อ่านได้ของ .wasm binary ต่อไปนี้คือ module ที่สมบูรณ์:

(module
(func (export "double") (param $n i32) (result i32)
local.get $n
i32.const 2
i32.mul))

มาแยกทุกบรรทัด:

  • (module ...) — wrapper ระดับบนสุด ทุกไฟล์ .wasm มี module เดียว
  • (func (export "double") ...) — ประกาศฟังก์ชันและทำให้ host มองเห็นได้ในชื่อ "double" ทันที ชื่อ export คือสิ่งที่ JavaScript เห็นบน instance.exports
  • (param $n i32) — ประกาศ parameter หนึ่งตัวชื่อ $n ประเภท i32 (32-bit signed integer) label $n เป็นความสะดวกของ WAT ที่ระดับ binary เป็นแค่ index
  • (result i32) — ประกาศว่าฟังก์ชันนี้ return i32 หนึ่งตัว
  • local.get $n — push ค่าปัจจุบันของ $n ลงบน operand stack
  • i32.const 2 — push ค่าคงที่ 2 ลงบน stack
  • i32.mul — pop ค่าบนสุดสองตัวของ stack, คูณเข้าด้วยกัน และ push ผลลัพธ์ เนื่องจากนี่คือคำสั่งสุดท้ายและ type ตรงกับ (result i32) ผลลัพธ์จึงกลายเป็น return value

การออกแบบ stack-based ของ Wasm หมายความว่าไม่มี return อย่างชัดเจน ค่าที่อยู่บนสุดของ stack เมื่อสิ้นสุด function body คือ return value

sandbox เปิดเผยฟังก์ชัน global compileWat(watString) ที่ขับเคลื่อนด้วย WABT library ฟังก์ชันนี้รับ WAT source string ของคุณแล้วคืน Promise<Uint8Array> ซึ่งคือ .wasm bytes ดิบที่ปกติคุณจะได้จากไฟล์บน disk หรือ network fetch

const bytes = await compileWat(wat);

WebAssembly.instantiate(bytes, importObject) คือ standard browser API สำหรับสร้าง Wasm instance ที่รันได้ โดยคืน Promise ที่ resolve เป็นคู่ { instance, module }:

  • instance — object ที่ live และรันได้ property .exports ของตัวเองมีฟังก์ชันและ memory ทั้งหมดที่ module export ออกมา
  • moduleWebAssembly.Module ที่คอมไพล์แล้วแต่ไม่มีชีวิต คุณสามารถแชร์และ instantiate ซ้ำได้โดยไม่ต้องคอมไพล์ใหม่

argument ที่สองคือ import object ที่เป็น nested object ของฟังก์ชัน, memory และ global ที่คุณ provide ให้ module จาก JavaScript เนื่องจากฟังก์ชัน double ของเราไม่ import อะไรจาก host เราจึงส่ง {}

const { instance } = await WebAssembly.instantiate(bytes, {});

เมื่อ instantiate แล้ว ทุกฟังก์ชันที่ export ออกมาจะใช้งานได้เป็น JavaScript function ธรรมดาบน instance.exports:

instance.exports.double(21); // → 42

JavaScript number ถูกแปลงอัตโนมัติไปเป็น/มาจาก i32 การเรียกเป็น synchronous ไม่ต้อง await

runner ด้านล่างคอมไพล์ WAT, instantiate และเรียก double ด้วย input ที่แตกต่างกันสามค่า ลองรันแล้วดูผลลัพธ์ใน console

WebAssembly
ตัวเลือกBenefitCost
เรียกฟังก์ชันผ่าน Wasm moduleความเร็วใกล้ native เมื่อ workload หนัก, ผลลัพธ์ deterministicต้องผ่าน loop compile → instantiate → call ทุกครั้งที่โหลด module ใหม่ ซึ่งมี overhead ที่ JS function ธรรมดาไม่มี
เรียกฟังก์ชัน JavaScript ตรงๆเรียกได้ทันทีไม่ต้อง compile/instantiate, debug ง่ายด้วย devtoolsไม่ได้ความเร็วระดับ native และไม่มี sandbox guarantee แบบ Wasm
  • เขียนฟังก์ชันง่ายๆ อย่าง double เป็น Wasm module แล้วคาดหวังว่าจะเร็วกว่า JS เสมอ ทั้งที่ function เล็กขนาดนี้ JS JIT compile ให้เร็วพอๆ กันอยู่แล้ว ความต่างจะเห็นชัดก็ต่อเมื่อ workload หนักจริง
  • ลืมว่า WebAssembly.instantiate ต้องรันทุกครั้งก่อนเรียก export ได้ แล้วพยายาม migrate ทั้งแอปไปเป็น Wasm module ย่อยจำนวนมาก ทำให้ต้อง compile/instantiate ซ้ำๆ โดยไม่จำเป็น
  • ไม่ cache WebAssembly.Module ที่ compile ไว้แล้วเมื่อต้อง instantiate module เดิมซ้ำหลายครั้ง ทำให้เสีย cost ของ compile step ไปโดยเปล่าประโยชน์ โดยเฉพาะกับ module เล็กที่ถูกเรียกไม่บ่อย

💡 ตัวอย่างจากของจริง

Figma ใช้ compile-instantiate-call loop แบบเดียวกับในบทนี้ (แต่ในสเกลใหญ่กว่ามาก) เพื่อรัน rendering engine ที่คอมไพล์จาก C++ เป็น Wasm ทำให้โหลดไฟล์ design เร็วขึ้นราว 3 เท่าเทียบกับตอนที่ยังเป็น pure JavaScript

`WebAssembly.instantiate(bytes, {})` คืนค่าอะไร?
object ว่าง `{}` ใน `WebAssembly.instantiate(bytes, {})` หมายถึงอะไร?
ในฟังก์ชัน WAT `double` คำสั่งใดที่สร้าง return value สุดท้าย?