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

Typed Array Views

เมื่อ module WebAssembly export memory ของตัวเอง JavaScript จะได้รับ WebAssembly.Memory object property .buffer ของตัวเองคือ ArrayBuffer มาตรฐาน — ชนิดเดิมที่ใช้ทั่ว Web Platform ซึ่งหมายความว่าคุณสามารถวาง typed-array view ใดก็ได้ทับลงไปเพื่ออ่านและเขียน memory ได้อย่างมีประสิทธิภาพ

instance.exports.mem.buffer เป็น ArrayBuffer คุณสามารถสร้าง typed view ทับลงไปโดยไม่ต้อง copy ข้อมูล:

  • new Uint8Array(buf) — แต่ละ element คือ unsigned byte หนึ่งตัว
  • new Int32Array(buf) — แต่ละ element คือ 4 byte ที่ตีความเป็น signed 32-bit integer
  • new Float64Array(buf) — แต่ละ element คือ 8 byte ที่ตีความเป็น 64-bit float
  • new DataView(buf) — การเข้าถึงระดับต่ำพร้อม method และการควบคุม endianness

View ทั้งหมดนี้แชร์ memory พื้นฐานเดียวกัน การเขียนผ่าน view หนึ่งจะมองเห็นได้ทันทีผ่าน view อื่นที่อยู่บน buffer เดียวกัน

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 bytes
console.log(u8[0], u8[1], u8[2], u8[3]); // 42 0 0 0

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 เดียวกันมองผ่าน “เลนส์” ต่างกันอย่างไร

WebAssembly
ตัวเลือกBenefitCost
ใช้ 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 และ Float32Array view แล้วแต่ operation ที่กำลังทำ โดยไม่ต้อง copy ข้อมูลภาพไปมาระหว่าง view แต่ละแบบ

ถ้า element ของ Int32Array อยู่ที่ index 2 จะอยู่ที่ byte offset เท่าใด?
integer หลาย bytes ถูกจัดเรียงอย่างไรใน WebAssembly linear memory?
JavaScript class ใดที่ควบคุม endianness ในการอ่าน/เขียน memory ได้อย่างสมบูรณ์?