Typed Array Views
เมื่อ module WebAssembly export memory ของตัวเอง JavaScript จะได้รับ WebAssembly.Memory object property .buffer ของตัวเองคือ ArrayBuffer มาตรฐาน — ชนิดเดิมที่ใช้ทั่ว Web Platform ซึ่งหมายความว่าคุณสามารถวาง typed-array view ใดก็ได้ทับลงไปเพื่ออ่านและเขียน memory ได้อย่างมีประสิทธิภาพ
ArrayBuffer และ typed views
หัวข้อที่มีชื่อว่า “ArrayBuffer และ typed views”instance.exports.mem.buffer เป็น ArrayBuffer คุณสามารถสร้าง typed view ทับลงไปโดยไม่ต้อง copy ข้อมูล:
new Uint8Array(buf)— แต่ละ element คือ unsigned byte หนึ่งตัวnew Int32Array(buf)— แต่ละ element คือ 4 byte ที่ตีความเป็น signed 32-bit integernew Float64Array(buf)— แต่ละ element คือ 8 byte ที่ตีความเป็น 64-bit floatnew DataView(buf)— การเข้าถึงระดับต่ำพร้อม method และการควบคุม endianness
View ทั้งหมดนี้แชร์ memory พื้นฐานเดียวกัน การเขียนผ่าน view หนึ่งจะมองเห็นได้ทันทีผ่าน view อื่นที่อยู่บน buffer เดียวกัน
Byte offset และ element index
หัวข้อที่มีชื่อว่า “Byte offset และ element index”Typed view ต่างชนิดมีขนาด element ต่างกัน ความสัมพันธ์ระหว่าง typed-array index กับ byte offset คือ:
byte offset = index × ขนาด element เป็น bytesตัวอย่างเช่น Int32Array มีขนาด element 4 ดังนั้น i32[2] อยู่ที่ byte offset 8 และ Float64Array มีขนาด element 8 ดังนั้น f64[1] อยู่ที่ byte offset 8 เช่นกัน — address เดียวกัน ตีความต่างกัน
const buf = instance.exports.mem.buffer;const u8 = new Uint8Array(buf);const i32 = new Int32Array(buf);const f64 = new Float64Array(buf);// Write an i32 at byte offset 0 (element index 0 of i32 view)i32[0] = 42;// Read the same 4 bytes as individual bytesconsole.log(u8[0], u8[1], u8[2], u8[3]); // 42 0 0 0Little-endian layout
หัวข้อที่มีชื่อว่า “Little-endian layout”WebAssembly memory เป็น little-endian: byte ที่มีนัยสำคัญน้อยที่สุดของค่าหลาย byte จะถูกเก็บที่ address ต่ำสุด ตัวอย่างเช่น ค่า 32-bit 0x01020304 ที่เก็บที่ offset 0 จะถูก layout ใน memory เป็น byte [04, 03, 02, 01]
สิ่งนี้สอดคล้องกับ x86/ARM และกับ typed array ของ JavaScript เองบนทุกแพลตฟอร์มในปัจจุบัน หากต้องการจัดการ endianness เฉพาะอย่างชัดเจน ใช้ DataView — method เช่น .getInt32(offset, true) รับ boolean isLittleEndian
Runner นี้เก็บค่าสองตัวจาก Wasm — 0x41424344 ที่ offset 0 และ 100 ที่ offset 8 — แล้วอ่านกลับผ่าน typed view หลายแบบเพื่อแสดงว่า byte เดียวกันมองผ่าน “เลนส์” ต่างกันอย่างไร
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| ใช้ TypedArray view หลายชนิดทับ buffer เดียวกัน | อ่าน/เขียนแบบ zero-copy โดยไม่ต้อง copy ข้อมูลข้าม view | ต้องสร้าง view ใหม่ทุกครั้งหลัง memory.grow เพราะ ArrayBuffer เดิมถูก detach |
| ตีความ byte เดียวกันด้วยหลาย type ตรงๆ (manual layout) | ยืดหยุ่น เข้าถึง field ขนาดต่างกันได้ตามต้องการ | ไม่มี type safety หรือ GC ช่วยตรวจ ผิด offset นิดเดียวก็อ่านค่าผิด type ทันที |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- สร้าง
Int32ArrayหรือFloat64Arrayที่ byte offset ซึ่งไม่ align กับขนาด element ของตัวเอง (เช่น offset ที่ไม่ใช่ multiple ของ 4 สำหรับInt32Array) ทำให้ constructor throw error - เก็บ view ที่สร้างไว้ก่อน
memory.growมาใช้ต่อ โดยลืมว่า ArrayBuffer เดิมถูก detach ไปแล้วหลัง grow ทำให้ view เก่าอ่านหรือเขียนค่าไม่ได้อีก - ลืมว่า WebAssembly memory เป็น little-endian เสมอ แล้วเผลอเรียก
DataView.getInt32(offset, false)(big-endian) ทำให้ byte ถูกตีความสลับลำดับผิด
💡 ตัวอย่างจากของจริง
เครื่องมือแก้ไขภาพใน browser สไตล์ Squoosh อ่าน raw pixel buffer เดียวกันผ่านทั้ง
Uint8ClampedArrayและFloat32Arrayview แล้วแต่ operation ที่กำลังทำ โดยไม่ต้อง copy ข้อมูลภาพไปมาระหว่าง view แต่ละแบบ