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

Conditionals: if, else และ select

if construct ใน WebAssembly ทำหน้าที่ทั้ง control-flow branch และ expression จะ pop ค่า i32 condition จาก stack แล้วรัน arm ใด arm หนึ่ง — และถ้ามี result type จะฝากค่าไว้บน stack ทำให้ if รู้สึกเหมือน ternary operator มากกว่า if statement แบบดั้งเดิม

if จะ pop i32 บนสุด ถ้าค่าไม่ใช่ศูนย์จะรัน then arm ถ้าเป็นศูนย์จะรัน else arm ที่เป็น optional construct สิ้นสุดด้วย end

(module
(func (export "sign") (param $x i32) (result i32)
local.get $x
i32.const 0
i32.gt_s ;; x > 0?
if (result i32)
i32.const 1 ;; then: return 1
else
i32.const -1 ;; else: return -1
end))

Annotation (result i32) ระบุว่า if expression จะฝากค่า i32 หนึ่งตัวบน stack ไม่ว่า arm ใดจะรัน ทั้งสอง arm ต้องฝากค่าจำนวนและชนิดเท่ากัน

เมื่อ condition ใช้เพื่อ side effect เท่านั้น — เช่น การเรียก imported function — สามารถละ result type ได้:

(module
(func (export "clampNeg") (param $x i32) (result i32)
local.get $x
i32.const 0
i32.lt_s
if
i32.const 0
local.set $x ;; clamp $x to 0 if negative
end
local.get $x))

ในกรณีนี้ if ไม่มี result แค่ conditionally update local

select เป็นทางเลือกที่กระชับกว่า if สำหรับการเลือกค่าง่ายๆ จะ pop สามค่า: condition, val_if_false, val_if_true (ตามลำดับ push: push true-val, push false-val, push condition) แล้ว push ค่าที่ถูกเลือก ไม่มีการ branch — CPU ประเมินทั้งสองค่าก่อน select

(module
(func (export "minVal") (param $a i32) (param $b i32) (result i32)
local.get $a ;; val if condition is true ($a <= $b)
local.get $b ;; val if condition is false
local.get $a
local.get $b
i32.le_s ;; condition: a <= b?
select)) ;; choose $a if true, $b if false
WebAssembly
ตัวเลือกBenefitCost
if/else แบบ structured expressionvalidate ได้ว่าทั้งสอง arm ฝากค่าชนิดและจำนวนเท่ากันตั้งแต่ compile time ปลอดภัยกว่า arbitrary gotoเขียน conditional ที่ซับซ้อนมากๆ (เช่นหลาย branch ไขว้กัน) ต้องซ้อน if หรือใช้ block ช่วย แทนการกระโดดตรงจุด
select (branchless)ไม่มี branch เลย เหมาะกับ hot path ที่ predict ยากต้องคำนวณทั้งสองค่าล่วงหน้าเสมอ แม้บาง arm จะไม่ได้ใช้จริง สิ้นเปลืองกว่าถ้าค่าใดค่าหนึ่งคำนวณแพง
  • ลืมให้ then และ else arm ฝากค่าชนิดและจำนวนเท่ากันตาม (result i32) ที่ประกาศไว้ ทำให้ module validate ไม่ผ่าน
  • ใช้ select แล้วคาดหวัง short-circuit เหมือน ternary ใน JavaScript ทั้งที่จริง engine ประเมิน (หรืออย่างน้อย push) ทั้งสองค่าเสมอก่อนเลือก
  • สับสน br_if กับ ifbr_if ไม่มี else arm ในตัวเอง แค่ conditionally branch ออกจาก block เดียว ไม่ใช่ two-way conditional แบบ if/else

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

เวลา Emscripten คอมไพล์ C/C++ ที่มี ternary หรือ if-else ซับซ้อน relooper algorithm (อัลกอริทึมที่ Emscripten พัฒนาขึ้นเอง) จะ reconstruct โครงสร้าง if/else ที่ validate ได้ให้ตรงกับ arbitrary control-flow graph ของโค้ดต้นฉบับ

`if` pop อะไรเพื่อตัดสินใจว่าจะรัน branch ไหน?
`select` ใน WAT คืออะไร?
keyword ใดที่ปิด `if` block ใน WAT?