Calls และ Recursion
ฟังก์ชันใน WebAssembly เรียกกันด้วยคำสั่ง call argument จะถูก push ขึ้น stack ก่อนเรียก และค่าที่คืนกลับ (ถ้ามี) จะถูกฝากไว้บน stack เมื่อ callee return Wasm รองรับ direct recursion อย่างสมบูรณ์ — ฟังก์ชันสามารถเรียกตัวเองได้ — และ mutual recursion ระหว่างฟังก์ชันที่กำหนดใน module เดียวกัน
คำสั่ง call
หัวข้อที่มีชื่อว่า “คำสั่ง call”call $funcname เรียกฟังก์ชันที่มีชื่อนั้น สามารถเรียกด้วย index ได้ด้วย (call 0) แต่ named reference อ่านง่ายกว่ามาก ก่อนรัน call ให้ push argument ทั้งหมดตามลำดับจากซ้ายไปขวา:
(module (func $double (param $n i32) (result i32) local.get $n i32.const 2 i32.mul) (func (export "quadruple") (param $x i32) (result i32) local.get $x call $double ;; passes $x; result replaces it on stack call $double)) ;; doubles againquadruple(3) เรียก $double สองครั้ง ได้ผลลัพธ์ 3 → 6 → 12
Recursive functions
หัวข้อที่มีชื่อว่า “Recursive functions”WAT อนุญาตให้ฟังก์ชันเรียกตัวเองได้ รูปแบบเหมือนทุกภาษา: push argument, call self, รวม result base case ใช้ if เพื่อ return ตรงๆ โดยไม่มี recursive call
(module (func $factorial (export "factorial") (param $n i32) (result i32) local.get $n i32.const 1 i32.le_s ;; n <= 1? if (result i32) i32.const 1 ;; base case: 0! = 1! = 1 else local.get $n local.get $n i32.const 1 i32.sub call $factorial ;; factorial(n-1) i32.mul ;; n * factorial(n-1) end))ฟังก์ชันนี้มีทั้ง internal name ($factorial) เพื่อเรียกตัวเองได้ และ export name เพื่อให้ JavaScript เรียกได้
ตัวอย่าง Fibonacci
หัวข้อที่มีชื่อว่า “ตัวอย่าง Fibonacci”การมี recursive call สองครั้งใน else arm ทำงานในลักษณะเดียวกัน — push argument, call, แล้วรวมผลลัพธ์ทั้งสอง:
(module (func $fib (export "fib") (param $n i32) (result i32) local.get $n i32.const 1 i32.le_s ;; n <= 1? if (result i32) local.get $n ;; fib(0)=0, fib(1)=1 else local.get $n i32.const 1 i32.sub call $fib ;; fib(n-1) local.get $n i32.const 2 i32.sub call $fib ;; fib(n-2) i32.add end))ทดลองรัน
หัวข้อที่มีชื่อว่า “ทดลองรัน”ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
Structured call (ไม่มี arbitrary jump ข้าม stack frame) | engine validate ได้ว่า call graph ถูกต้องตั้งแต่ compile time ปลอดภัยกว่าการกระโดดข้าม address ตรงๆ | ไม่มี manual stack manipulation แบบ assembly ทำให้ pattern บางอย่าง (เช่น coroutine แบบเดิม) ต้องเขียนใหม่ |
Recursion ผ่าน call ธรรมดา | เขียนง่าย เข้าใจง่าย เหมือน recursive function ในภาษาอื่น | Wasm ไม่มี automatic stack growth เหมือน native thread บางตัว recursion ลึกเกินไปจะ trap ด้วย stack exhaustion |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- เขียน recursive function โดยไม่มี base case ที่ครอบคลุมครบ (เช่นลืมเช็ค
n < 0) ทำให้ recursion ไม่มีวันจบ - คาดหวังว่า Wasm จะทำ tail-call optimization ให้อัตโนมัติเหมือนบางภาษา แต่ทุก
callเพิ่ม frame เข้า call stack เสมอ ทำให้ recursion ลึกๆ เสี่ยง stack exhaustion - push argument ผิดลำดับก่อน
call(WAT ไม่มี named argument) ทำให้ parameter ไปผูกกับตำแหน่งผิดโดยไม่มี error ตอน compile
💡 ตัวอย่างจากของจริง
Wasmtime ใช้ structured call stack ของ Wasm ในการ verify และ compile function call ให้เร็วโดยไม่ต้องวิเคราะห์ arbitrary jump และยัง enforce stack size limit เพื่อจับ unbounded recursion ก่อนที่จะ crash process จริง