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

Branches: block, br, br_if, br_table

การ branch ใน WebAssembly เกิดขึ้นเทียบกับ labeled construct เสมอ — ไม่ใช่กับ raw memory address กลุ่มคำสั่ง br จะชี้ไปที่ nesting level ไม่ใช่หมายเลขบรรทัด ดังนั้นพฤติกรรมของ br จึงขึ้นอยู่กับชนิด construct ที่ชี้ถึงอย่างสิ้นเชิง

block คือกลุ่มคำสั่งที่เดินไปข้างหน้าเท่านั้น เมื่อ br $label ชี้ที่ block execution จะกระโดดไปยังคำสั่งที่อยู่ หลัง end ของ block ทันที ให้คิดว่าเป็น labeled break

(module
(func (export "earlyExit") (result i32)
(block $b (result i32)
i32.const 42
br $b ;; exits the block; 42 is the block result
i32.const 99) ;; never reached
))

Annotation (result i32) บน block ระบุว่า block จะฝากค่า i32 หนึ่งตัวบน stack เมื่อออก — ไม่ว่าจะ fall through ถึง end หรือกระโดดออกด้วย br

คำสั่ง br เดียวกันจะทำงานต่างกันขึ้นอยู่กับสิ่งที่ชี้:

flowchart LR
  A["block $b\n  ...body...\n  br $b"] --> B["jumps to END\nof block (forward)"]
  C["loop $l\n  ...body...\n  br $l"] --> D["jumps to START\nof loop (backward)"]
br ใน block ออกไปข้างหน้า; br ใน loop วนซ้ำไปข้างหลัง

br_if $label pop ค่า i32 condition จาก stack ถ้าไม่ใช่ศูนย์จะ branch ถ้าเป็นศูนย์จะ execute คำสั่งถัดไปต่อไป นี่คือวิธีมาตรฐานในการเขียน conditional early exit

(module
(func (export "relu") (param $x i32) (result i32)
(block $done (result i32)
i32.const 0 ;; block result if we exit early
local.get $x
i32.const 0
i32.lt_s ;; x < 0?
br_if $done ;; if yes, exit block — 0 is already on stack
drop ;; discard the 0; x >= 0
local.get $x))) ;; return x

br_table รับรายการ label และค่า default จะ pop ค่า i32 index แล้วกระโดดไปที่ label ที่ตำแหน่ง index นั้น หรือไปที่ default ถ้า index เกินขอบเขต เทียบได้กับ switch statement ใน WAT

(module
(func (export "describe") (param $n i32) (result i32)
(block $two
(block $one
(block $zero
local.get $n
br_table $zero $one $two $two) ;; index 0→$zero, 1→$one, ≥2→$two
i32.const 0) ;; $zero case: return 0
i32.const 1) ;; $one case: return 1
i32.const 2)) ;; $two case: return 2
WebAssembly
ตัวเลือกBenefitCost
Structured control flow (block/loop/br)validate ได้แบบ static และปลอดภัยกว่า เพราะไม่มี arbitrary goto ที่กระโดดไปที่ไหนก็ได้เขียน control flow ที่ซับซ้อนยากขึ้น ต้องซ้อน block หลายชั้นแทนการ jump ตรงจุด
ไม่มี raw jump แบบ assembly-styleengine ตรวจสอบ label ทุกตัวได้ตั้งแต่ compile time ลด security riskCFG ที่มาจาก native code (เช่นมี goto เยอะๆ) ต้องแปลงผ่าน algorithm อย่าง relooper ก่อนถึงจะ map เข้า Wasm ได้
  • นับ nesting depth ผิดเวลามี block ซ้อนกันหลายชั้น ทำให้ br หรือ br_table ชี้ผิด label (off-by-one ใน depth)
  • ลืมว่า br_table ต้องมี default label เสมอ ดัชนีที่เกินขอบเขตของรายการทั้งหมดจะตกไปที่ default โดยอัตโนมัติ
  • สับสนทิศทางของ br เมื่อเปลี่ยนจาก block ไป loopbr ตัวเดียวกัน forward ใน block แต่ backward ใน loop ถ้าไม่ระวัง label ก็จะพา flow ไปผิดทาง

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

Emscripten ใช้ relooper algorithm (อัลกอริทึมที่ Emscripten พัฒนาขึ้นเอง) แปลง arbitrary control-flow graph จากโค้ด C/C++ ให้กลายเป็น structured block/loop/br ที่ Wasm ต้องการ เพราะ native code เต็มไปด้วย goto ที่ Wasm ไม่รองรับตรงๆ

`br $b` กระโดดไปที่ไหนเมื่อ `$b` เป็น `block` label?
`br_if $l` pop อะไรออกจาก stack?
คำสั่งใดใน WAT ที่นำ jump table มาใช้?