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

คอมไพล์และสร้าง Instance

การโหลด WebAssembly module จาก JavaScript เป็น process สองขั้นตอน: compile ไบต์ให้กลายเป็น module artifact ที่ผ่านการตรวจสอบแล้ว จากนั้น instantiate module นั้นเพื่อให้ได้ instance ที่ทำงานได้จริงพร้อม exports ที่เรียกใช้ได้ ตัวแปรส่วนกลาง WebAssembly ของ browser เปิดเผย API เฉพาะสำหรับทั้งสองขั้นตอน รวมถึง shortcut ที่สะดวกซึ่งทำทั้งสองอย่างในคราวเดียว

WebAssembly.compile(bufferSource) รับ BufferSource ใดก็ได้ (เช่น Uint8Array, ArrayBuffer หรือรูปแบบที่คล้ายกัน) และคืนค่าเป็น Promise<WebAssembly.Module> ผลลัพธ์ Module ที่ได้คือ artifact ที่คอมไพล์และตรวจสอบแล้ว — browser ได้แยกวิเคราะห์และปรับแต่งไบต์แล้ว แต่ยังไม่มีการรันโค้ดใดเลย

const response = await fetch('module.wasm');
const bytes = await response.arrayBuffer();
const module = await WebAssembly.compile(bytes);
// `module` can be cached, cloned, or passed to a Worker via postMessage

เนื่องจากออบเจกต์ Module สามารถ serialize ได้ จึงเก็บไว้ใน IndexedDB หรือส่งไปยัง thread อื่นด้วย postMessage ได้ — คุณจ่ายค่าใช้จ่ายการคอมไพล์เพียงครั้งเดียวและนำผลลัพธ์มาใช้ซ้ำได้

WebAssembly.instantiate() มีสอง overload:

// Overload 1 — pass raw bytes: compiles AND instantiates in one call
const { module, instance } = await WebAssembly.instantiate(bytes, importObject);
// Overload 2 — pass an already-compiled Module: instantiates only
const instance = await WebAssembly.instantiate(module, importObject);
// Note: this overload returns a WebAssembly.Instance directly, not {module, instance}

Overload แรกเป็นจุดเริ่มต้นที่พบบ่อยที่สุด ส่วน overload ที่สองมีประโยชน์เมื่อต้องการ instantiate module ที่คอมไพล์แล้วหลายครั้ง เช่น เพื่อสร้าง sandbox ที่แยกจากกันและไม่แชร์ state ใดๆ

หลังจาก instantiation แล้ว instance.exports คือออบเจกต์ JavaScript ธรรมดา โดย key ของตัวเองคือชื่อที่ประกาศไว้ในส่วน export ของ Wasm module ทุกฟังก์ชัน memory, global หรือ table ที่ export จะปรากฏเป็น property ฟังก์ชันที่ export ออกมาทำงานเหมือนฟังก์ชัน JS ทั่วไป — เรียกใช้ได้เลย ส่งตัวเลข JS เข้าไป และรับตัวเลข JS กลับมา

const { instance } = await WebAssembly.instantiate(bytes, {});
const { square, cube } = instance.exports;
console.log(square(5)); // 25
console.log(cube(3)); // 27

WAT module ที่กำหนด exports เหล่านั้นมีหน้าตาดังนี้:

(module
(func (export "square") (param $n i32) (result i32)
local.get $n
local.get $n
i32.mul)
(func (export "cube") (param $n i32) (result i32)
local.get $n
local.get $n
i32.mul
local.get $n
i32.mul))

ตัวรันด้านล่างจะคอมไพล์ module, instantiate และเรียกใช้ทั้งสอง export

WebAssembly
ตัวเลือกBenefitCost
instantiateStreamingคอมไพล์ทับซ้อนกับการดาวน์โหลด ได้ instance พร้อมใช้เร็วที่สุดต้องการ Content-Type: application/wasm จาก server ถ้าผิดจะ reject ด้วย TypeError ทันที
compile + instantiate แยกขั้นตอนควบคุมได้ละเอียด เช่น cache Module ไว้ใช้ instantiate ซ้ำหลายครั้งโดยไม่ต้องคอมไพล์ใหม่ต้องมี buffer เต็มไฟล์อยู่ในหน่วยความจำก่อนจึงจะเริ่มคอมไพล์ได้
  • โหลดไฟล์ด้วย arrayBuffer() ก่อนเสมอแล้วค่อยเรียก WebAssembly.instantiate() ทั้งที่ควรใช้ instantiateStreaming เพื่อคอมไพล์ไปพร้อมกับดาวน์โหลด
  • คอมไพล์ module ใหม่ทุกครั้งที่ต้องการ instance เพิ่ม แทนที่จะแยก WebAssembly.compile() ออกมาครั้งเดียวแล้วเรียก WebAssembly.instantiate(module, ...) ซ้ำจาก Module เดิม
  • ลืมว่า export ที่เป็น i64 จะกลับมาเป็น BigInt แล้วเอาไปบวก/คูณกับ number ตรงๆ โดยไม่แปลงชนิดก่อน ทำให้โค้ด throw error

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

Squoosh คอมไพล์ Wasm codec หลายตัวไว้ล่วงหน้าเพียงครั้งเดียว แล้ว instantiate ใหม่ทุกครั้งที่ผู้ใช้เลือกบีบอัดภาพด้วย codec นั้น เพื่อเลี่ยงต้นทุนการคอมไพล์ซ้ำซ้อนในทุก session

WebAssembly.instantiate(bytes, {}) คืนค่าอะไร?
property ไหนของผลลัพธ์จาก instantiation ที่เก็บ exported functions ที่เรียกใช้ได้?
WebAssembly.Module ที่ compile แล้ว instantiate ซ้ำได้หลายครั้งไหม?