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

การขยาย Memory และ Data Segment

module WebAssembly เริ่มต้นด้วยจำนวนหน้าที่คุณประกาศใน (memory N) แต่บางครั้งคุณต้องการ memory เพิ่มขณะ runtime — เช่น เมื่อผู้ใช้อัปโหลดไฟล์ขนาดใหญ่หรือข้อมูลของคุณเพิ่มขึ้นเกินที่คาดไว้ คำสั่ง memory.grow และ segment (data ...) ครอบคลุมสองเรื่องที่เกี่ยวข้องกัน: การขยาย memory และการเติม byte แบบ static ล่วงหน้า

memory.grow รับ argument i32 หนึ่งตัวจาก stack: จำนวนหน้าเพิ่มเติมที่ต้องการ allocate คืนค่าขนาด เดิม เป็นหน้า หรือ -1 ถ้า grow ล้มเหลว (เช่น หน่วยความจำไม่พอหรือเกิน maximum ที่ประกาศไว้)

(module
(memory (export "mem") 1)
(func (export "grow") (param $pages i32) (result i32)
local.get $pages
memory.grow))

คุณยังสามารถ grow memory จากฝั่ง JavaScript ด้วย mem.grow(n):

const mem = instance.exports.mem;
const oldPages = mem.grow(1); // returns previous page count, or -1

นี่คือจุดสำคัญมากที่สุด: เมื่อ WebAssembly memory ขยาย ArrayBuffer พื้นฐานจะถูก detach typed view ใดก็ตามที่สร้างจาก buffer เดิมจะกลายเป็นแบบว่างเปล่าและใช้งานไม่ได้ คุณต้องสร้าง view ใหม่ทั้งหมดจาก mem.buffer ทันทีหลังจาก grow ทุกครั้ง

let view = new Uint8Array(mem.buffer); // valid before grow
mem.grow(1);
// view is now detached — do NOT use it
view = new Uint8Array(mem.buffer); // re-create from fresh buffer

Segment (data ...) ช่วยให้คุณฝัง byte แบบ static โดยตรงใน module และให้ byte เหล่านั้นถูกเขียนลง memory โดยอัตโนมัติตอน instantiation นี่คือวิธีที่ compiled language เริ่มต้น string literal และ read-only table

(module
(memory (export "mem") 1)
(data (i32.const 0) "Hello")) ;; writes 5 UTF-8 bytes at offset 0

Byte จะถูก copy จาก module ลง memory ก่อนที่ฟังก์ชันใดจะรัน ดังนั้นข้อมูลจะพร้อมใช้งานทันทีเมื่อฟังก์ชัน export แรกถูกเรียก

ตัวอย่างด้านล่างใช้ segment (data ...) เขียน "Hi" ที่ offset 0 ขยาย memory หนึ่งหน้า สร้าง typed-array view ใหม่หลัง grow และตรวจสอบว่าทั้งขนาดใหม่และข้อมูลเดิมถูกต้อง

WebAssembly
ตัวเลือกBenefitCost
TypedArray view ที่สร้างจาก mem.bufferเข้าถึง byte แบบ zero-copy รวดเร็ว ไม่มี overhead จากการ copyต้องสร้าง view ใหม่ทุกครั้งหลัง memory.grow เพราะ buffer เดิมถูก detach ไปแล้ว
จัดการ growth และ layout ของ memory เองคุมได้ว่าจะขยายเมื่อไหร่ ขนาดเท่าไหร่ไม่มี GC ช่วยตรวจ ต้อง track เองว่า pointer หรือ view ตัวไหนยังใช้งานได้อยู่หลัง grow
  • Cache typed-array view ไว้ก่อนเรียก memory.grow แล้วลืมสร้างใหม่ ทำให้ view ชี้ไปยัง buffer ที่ถูก detach ไปแล้ว อ่านหรือเขียนค่าผิดแบบเงียบๆ โดยไม่มี error เตือน
  • คิดว่า memory.grow(n) รับจำนวน byte ทั้งที่จริงรับเป็นจำนวน page (แต่ละ page 64 KiB) ทำให้เผลอขอ memory เกินความจำเป็นไปมาก
  • วาง (data ...) segment ที่ offset overlap กับพื้นที่ที่ JavaScript จะเขียนทับภายหลัง ทำให้ข้อมูลเริ่มต้นถูกเขียนทับโดยไม่ตั้งใจ

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

FFmpeg.wasm ใช้ linear memory buffer ที่ขยายได้ (memory.grow) เพื่อ stream เฟรมวิดีโอที่ decode แล้วระหว่าง JavaScript กับ Wasm โดยตรง โดยไม่ต้อง copy เฟรมทั้งหมดกลับไปมาทุกครั้งที่หน่วยความจำไม่พอ

คำสั่ง memory.grow คืนค่าอะไร?
เกิดอะไรกับ typed-array views ที่มีอยู่เมื่อ WebAssembly memory ขยาย?
(data (i32.const 0) "Hello") segment ทำอะไร?