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

การอ่าน Exports

หลังจาก instantiation แล้ว instance.exports คือ JavaScript object ธรรมดา ทุก symbol ที่ WAT module ประกาศ export ไว้จะปรากฏเป็น property บน object นั้น — exported functions สามารถเรียกใช้ได้เหมือนฟังก์ชันปกติ, exported memory จะเป็น WebAssembly.Memory, และ exported globals จะเป็น WebAssembly.Global objects

Exported Wasm functions ทำงานเหมือน JavaScript functions ทั่วไป คุณเรียกใช้ได้โดยตรง ส่ง JavaScript numbers เป็น arguments และรับค่า JavaScript กลับมา การแมปสำหรับ numeric types ส่วนใหญ่นั้นตรงไปตรงมา: i32, f32 และ f64 ทั้งหมดจะกลายเป็น number ใน JavaScript

(module
(func (export "add") (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add))
const { instance } = await WebAssembly.instantiate(bytes, {});
console.log(instance.exports.add(10, 32)); // 42

ประเภท number ของ JavaScript ใช้มาตรฐาน IEEE-754 double precision ซึ่งสามารถแทนค่าจำนวนเต็มได้แม่นยำเพียงถึง 2^53 − 1 เท่านั้น (Number.MAX_SAFE_INTEGER) ส่วน Wasm i64 คือจำนวนเต็มขนาด 64 บิตเต็ม ซึ่งรองรับค่าที่เกินช่วงนั้นมาก

เพื่อป้องกันการสูญเสียความแม่นยำโดยไม่ตั้งใจ WebAssembly JS API จะแปลงค่า return ประเภท i64 ให้เป็น JavaScript BigInt แทนที่จะเป็น number ผลลัพธ์จากการเรียกฟังก์ชันที่คืนค่า i64 จะมี typeof result === 'bigint' ไม่ใช่ 'number'

(module
(func (export "bignum") (result i64)
i64.const 9007199254740993))
const result = instance.exports.bignum();
console.log(result); // 9007199254740993n
console.log(typeof result); // "bigint"

สังเกตว่าการคำนวณระหว่าง BigInt กับ number ต้องแปลงชนิดข้อมูลก่อนอย่างชัดเจน — ไม่สามารถใช้ตัวดำเนินการอย่าง + หรือ * ร่วมกันได้โดยตรงหากไม่แปลงชนิดก่อน

Wasm module สามารถ export globals ร่วมกับ functions ได้ ค่า global ที่ถูก export จะถูกเข้าถึงใน JavaScript ในรูปแบบ WebAssembly.Global object โดยค่าตัวเลขปัจจุบันจะอยู่ที่ property .value

(module
(global (export "PI") f64 (f64.const 3.14159)))
console.log(instance.exports.PI.value); // 3.14159

เมื่อ module export linear memory ออกมา JavaScript จะได้รับ WebAssembly.Memory object bytes ดิบสามารถเข้าถึงได้ผ่าน property .buffer ในรูปแบบ ArrayBuffer ซึ่งคุณสามารถห่อด้วย typed array view เพื่ออ่านหรือเขียนข้อมูลแต่ละ byte ได้

(module
(memory (export "mem") 1))
const mem = instance.exports.mem; // WebAssembly.Memory
const view = new Uint8Array(mem.buffer);
console.log(view.length); // 65536 (1 page = 64 KiB)

Module ด้านล่างนี้ export ฟังก์ชัน add, ฟังก์ชัน bignum ที่คืนค่า i64 ซึ่งเกิน Number.MAX_SAFE_INTEGER, และ global VERSION ตัวรันจะอ่านค่าทั้งสามและแสดงค่าพร้อมชนิดข้อมูล

WebAssembly
ตัวเลือกBenefitCost
instantiateStreamingเริ่มคอมไพล์ทันทีที่ bytes เริ่มไหลมาจาก network ไม่ต้องรอดาวน์โหลดครบ ทำให้ exports พร้อมใช้เร็วขึ้นต้องพึ่ง server ที่ตั้งค่า Content-Type: application/wasm ให้ถูกต้อง ไม่งั้น reject ทันที
instantiate แบบ buffer เต็มไฟล์เขียนง่าย ควบคุม flow ได้ตรงไปตรงมา ใช้ได้แม้ไม่ควบคุม MIME type ของ serverต้องรอดาวน์โหลดครบก่อนเริ่มคอมไพล์ exports จึงพร้อมใช้ช้ากว่า
  • ลืมแปลงผลลัพธ์ที่เป็น i64 ให้เป็น BigInt ก่อนนำไปคำนวณกับ number ธรรมดา ทำให้โค้ดโยน TypeError หรือปัดค่าคลาดเคลื่อนแบบเงียบๆ
  • อ่าน/เขียนข้อมูลก้อนใหญ่ผ่าน exported memory ทีละไบต์หรือทีละ call แทนที่จะสร้าง typed array view ครอบ .buffer แล้วอ่าน-เขียนรวดเดียว ทำให้เสีย performance โดยไม่จำเป็น
  • โหลดทั้งไฟล์ด้วย arrayBuffer() ก่อนแล้วค่อย instantiate เพื่อไปอ่าน exports แทนที่จะใช้ instantiateStreaming ซึ่งคอมไพล์ไปพร้อมกับการดาวน์โหลด

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

Squoosh (เครื่องมือบีบอัดภาพของ Google) export ฟังก์ชัน codec หลายตัว (MozJPEG, WebP, AVIF ฯลฯ) จาก Wasm module แล้วเรียกใช้ exports เหล่านั้นตรงๆ จาก JS พร้อมอ่านผลลัพธ์ภาพผ่าน exported memory เพื่อให้ได้ throughput สูงสุด

ค่า Wasm i64 ที่ถูก export จะกลายเป็น JavaScript type ใด?
จะอ่านค่าของ Wasm global ที่ถูก export ใน JavaScript ได้อย่างไร?
เหตุใด Wasm จึงใช้ BigInt สำหรับ i64 แทนที่จะเป็น number?