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

module และฟังก์ชัน

โค้ด WebAssembly ทุกชิ้นอยู่ภายใน module module เป็นหน่วยระดับบนสุดของการคอมไพล์และการดีพลอย — module คือสิ่งที่คุณคอมไพล์ ส่งผ่านเครือข่าย และ instantiate ใน browser ภายใน module คุณกำหนดฟังก์ชัน ประกาศหน่วยความจำ import จาก JavaScript และเลือกว่าจะ export อะไร

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

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

WebAssembly
ตัวเลือกBenefitCost
เขียน 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 ตรงกับที่ต้องการจริงหรือไม่

ไวยากรณ์ใดที่ export ฟังก์ชันจากโมดูล WAT?
JavaScript สามารถเรียกฟังก์ชันที่ถูกกำหนดไว้แต่ไม่ได้ถูก export ได้หรือไม่?
s-expression ที่อยู่นอกสุดในไฟล์ WAT ทุกไฟล์คืออะไร?