module และฟังก์ชัน
โค้ด WebAssembly ทุกชิ้นอยู่ภายใน module module เป็นหน่วยระดับบนสุดของการคอมไพล์และการดีพลอย — module คือสิ่งที่คุณคอมไพล์ ส่งผ่านเครือข่าย และ instantiate ใน browser ภายใน module คุณกำหนดฟังก์ชัน ประกาศหน่วยความจำ import จาก JavaScript และเลือกว่าจะ export อะไร
ตัวห่อ module
หัวข้อที่มีชื่อว่า “ตัวห่อ module”s-expression ที่อยู่นอกสุดในไฟล์ WAT ทุกไฟล์คือ (module ...) ทุกอย่างที่เหลือซ้อนอยู่ภายใน:
(module ;; functions, imports, exports, memory, globals go here)module ที่ไม่มีเนื้อหาเป็น WAT ที่ถูกต้องและคอมไพล์ออกมาเป็นไฟล์ .wasm เล็ก ๆ — เพียงแต่ยังไม่มีอะไรที่มีประโยชน์อยู่ในนั้น
การประกาศฟังก์ชัน
หัวข้อที่มีชื่อว่า “การประกาศฟังก์ชัน”ฟังก์ชันถูกประกาศด้วย (func ...) ฟังก์ชันที่ไม่รับอาร์กิวเมนต์และไม่คืนค่าอะไรเลยมีหน้าตาแบบนี้:
(module (func ;; empty body — does nothing ))หากต้องการรับอาร์กิวเมนต์ คุณเพิ่มแอตทริบิวต์ (param <name> <type>) หากต้องการประกาศชนิดของค่าที่คืนกลับ คุณเพิ่ม (result <type>) ชื่อพารามิเตอร์ขึ้นต้นด้วย $ และเป็นทางเลือก แต่แนะนำอย่างยิ่งเพื่อความอ่านง่าย:
(module (func (param $x i32) (param $y i32) (result i32) local.get $x local.get $y i32.mul))module นี้คอมไพล์ได้ดี แต่ฟังก์ชันนั้นเข้าถึงไม่ได้เลยจาก JavaScript เพราะยังไม่ได้ถูก export
การ export ฟังก์ชัน
หัวข้อที่มีชื่อว่า “การ export ฟังก์ชัน”JavaScript สามารถเรียกได้เฉพาะฟังก์ชันที่ module export ออกมาอย่างชัดเจนเท่านั้น มีไวยากรณ์ที่เทียบเท่ากันสองแบบ
คำสั่ง export แยกต่างหาก — กำหนดฟังก์ชันพร้อมชื่อ แล้ว export ทีหลัง:
(module (func $multiply (param $x i32) (param $y i32) (result i32) local.get $x local.get $y i32.mul) (export "multiply" (func $multiply)))Inline export — แนบการ export เข้ากับการประกาศ func โดยตรง:
(module (func (export "multiply") (param $x i32) (param $y i32) (result i32) local.get $x local.get $y i32.mul))ทั้งสองรูปแบบสร้างไบต์ .wasm ที่เหมือนกัน สไตล์ inline กระชับกว่า ส่วนสไตล์คำสั่งแยกต่างหากมีประโยชน์เมื่อคุณต้องการ export ฟังก์ชันเดียวกันภายใต้หลายชื่อ หรือต้องการแยกการประกาศกับการ export ให้เห็นชัดเจน
ลองรันดู
หัวข้อที่มีชื่อว่า “ลองรันดู”ตัวอย่างด้านล่างกำหนดฟังก์ชัน multiply โดยใช้ไวยากรณ์ inline export และเรียกใช้จาก JavaScript
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| เขียน WAT เอง | เห็นโครงสร้าง (module ...) และ (func ...) ตรง ๆ เข้าใจว่า export ทำงานยังไงในระดับ binary format | ต้องเขียน boilerplate เองทั้งหมด ไม่มี type system ระดับสูงช่วยเช็คให้ก่อน compile |
| ปล่อยให้ compiler generate Wasm (Rust/C/AssemblyScript) | ได้ module ที่มี memory layout, import/export จัดการให้อัตโนมัติ เหมาะกับ codebase ขนาดใหญ่ | อ่าน .wasm ที่ได้ตรง ๆ ยาก ต้องพึ่งเครื่องมือแปลงกลับเป็น WAT เพื่อ debug |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- ประกาศ
(result i32)ไว้แต่ตัว body ของฟังก์ชันจบลงโดยไม่มีค่าi32เหลืออยู่บน stack พอดีหนึ่งตัว เช่นมี branch ออกจากฟังก์ชันกลางทางโดยไม่ push ค่ากลับ ทำให้ module validation ไม่ผ่าน - สลับลำดับ operand ตอน push ขึ้น stack ก่อนเรียก instruction ภายในตัว body ของ
funcทำให้ผลลัพธ์ที่ export ออกไปผิด ทั้งที่ syntax ถูกต้องทุกอย่าง - ใช้
br/br_ifกระโดดออกจาก block ภายในฟังก์ชันโดยไม่คำนึงว่า target ของ branch คาดหวังอะไรอยู่บน stack ทำให้ signature ของfuncกับสภาพ stack จริงไม่ตรงกัน
💡 ตัวอย่างจากของจริง
ทีมที่ debug production
.wasmที่บริษัทอย่าง Cloudflare Workers มักใช้wasm2watแปลง binary module กลับมาเป็น WAT ที่อ่านได้ เพื่อตรวจสอบว่าฟังก์ชันที่ export ออกไปมี signature และ body ตรงกับที่ต้องการจริงหรือไม่