Is it possible to Export Memory as Shared in WebAssembly?

185 views Asked by At
(module
  (memory (export "memory") 1)
  (data (i32.const 0) "Hello World!")
)
fetch('hello.wasm')
  .then(response => response.arrayBuffer())
  .then(bytes => WebAssembly.instantiate(bytes))
  .then(result => {
    // Access the memory
    const memory = result.instance.exports.memory;
    const buffer = new Uint8Array(memory.buffer);

    // Use the memory
    const offset = 0; // Start offset
    const length = 13; // Length of the string to read
    const message = new TextDecoder().decode(buffer.slice(offset, offset + length));
    console.log(message); // This should print "Hello, World!"

    // Check if the memory is shared
    if (memory.buffer instanceof SharedArrayBuffer) {
      console.log("WebAssembly memory is shared.");
    } else {
      console.log("WebAssembly memory is not shared.");
    }
  })
  .catch(error => console.error(error));

Data type of "memory.buffer" is ArrayBuffer and therefore cannot be used as shared data in a worker. Is there a way to make the exported memory usable as shared?

2

There are 2 answers

2
Andreas Rossberg On

You have to declare it as shared:

(module
  (memory (export "memory") 1 1 shared)
  (data (i32.const 0) "Hello World!")
)
2
a.gulcan On

When I change the flag to 0, it causes an error. That's why it works correctly only when I change the flags that are set to 1.

  const code = (new TextEncoder()).encode('\x00\x61\x73\x6d\x01\x00\x00\x00\x05\x04\x01\x01\x01\x01\x07\x0a\x01\x06\x6d\x65\x6d\x6f\x72\x79\x02\x00\x0b\x12\x01\x00\x41\x00\x0b\x0c\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21\x00\x08\x04\x6e\x61\x6d\x65\x02\x01\x00');
async function runner(code) {
const result=await WebAssembly.instantiate(code)
  try{
    // Access the memory
    const memory = result.instance.exports.memory;
    const buffer = new Uint8Array(memory.buffer);

    // Use the memory
    const offset = 0; // Start offset
    const length = 13; // Length of the string to read
    const message = new TextDecoder().decode(buffer.slice(offset, offset + length));
    console.log(message); // This should print "Hello, World!"

    // Check if the memory is shared
    if (memory.buffer instanceof ArrayBuffer) {
       console.log(`WebAssembly memory is not shared.(${memory.buffer.constructor.name})`);
    } else {
     console.log(`WebAssembly memory is shared.(${memory.buffer.constructor.name})`);
    }
  }catch(error){ console.error(error)};
}
runner(code);
function decodeLEB128(bytes, offset) {
  let result = 0;
  let shift = 0;
  let byte;

  do {
    byte = bytes[offset];
    result |= (byte & 0x7f) << shift;
    shift += 7;
    offset++;
  } while (byte & 0x80);

  return [result, offset];
}

// Load or fetch the WebAssembly binary file
const buffer=code.buffer
  try{
    const buffer=code.buffer
    const view = new DataView(buffer);

    // Skip the header information (WASM_BINARY_MAGIC and WASM_BINARY_VERSION)
    let headerOffset = 8; // Skip the first 8 bytes (4 bytes MAGIC, 4 bytes VERSION)

    const sectionNames = {
      0: 'Custom',
      1: 'Type',
      2: 'Import',
      3: 'Function',
      4: 'Table',
      5: 'Memory',
      6: 'Global',
      7: 'Export',
      8: 'Start',
      9: 'Element',
      10: 'Code',
      11: 'Data',
    };

    const sectionInfo = {};
    const memories = [];

    // Iterate through the sections and collect information
    for (let i = headerOffset; i < buffer.byteLength;) {
      const sectionId = view.getUint8(i, true); // Read the Section ID
      i++;
      let sectionSize, sectionCount;
      let start;
      [sectionSize, i] = decodeLEB128(new Uint8Array(buffer), i);
      [sectionCount, start] = decodeLEB128(new Uint8Array(buffer), i); // Read the size encoded with LEB128

      const sectionName = sectionNames[sectionId];
      if (!sectionInfo[sectionId]) {
        sectionInfo[sectionId] = {
          start: i,
          size: sectionSize,
          count: sectionCount,
        };
      } else {
        sectionInfo[sectionId].size += sectionSize;
        sectionInfo[sectionId].count += sectionCount;
      }

      if (sectionId === 5) { // Memory Section
        for (let j = start; j < i + sectionSize;) {
          let flag = view.getUint8(j, true);
          if (flag === 1) {
            view.setUint8(j, 3);
            flag = 3;
          }
          j++;
          let initial;
          let maximum;

          if (flag === 0) {
            [initial, j] = decodeLEB128(new Uint8Array(buffer), j);
          } else if (flag === 1 || flag === 3) {
            [initial, j] = decodeLEB128(new Uint8Array(buffer), j);
            [maximum, j] = decodeLEB128(new Uint8Array(buffer), j);
          }

          memories.push({ flag, initial, maximum });
        }
      }

      // Skip other sections
      i += sectionSize;
    }

    // Write to the HTML document
    const resultContainer = document.getElementById('result-container');

    // Print in sorted order by the start value
    const sortedSectionInfo = Object.entries(sectionInfo).sort((a, b) => a[1].start - b[1].start);
    let html = "<h2>Sections:</h2><ul>";

    for (const [sectionId, section] of sortedSectionInfo) {
      const end = section.start + section.size;
      html += `<li>${sectionNames[sectionId]} start=0x${section.start.toString(16)} end=0x${end.toString(16)} (size=0x${section.size.toString(16)}) count: ${section.count}</li>`;
    }
    html += "</ul>";

    // Print the memories array
    html += "<h2>Memories:</h2><ul>";
    memories.forEach((memory, index) => {
      html += `<li>Memory ${index + 1}: Flag=${memory.flag}, Initial=${memory.initial}, Maximum=${memory.maximum}</li>`;
    });
    html += "</ul>";

    resultContainer.innerHTML = html;
runner(buffer);
  }catch(error) {
    const errorText = "Error: " + error.message;
    const errorElement = document.createElement('div');
    errorElement.textContent = errorText;
    document.body.appendChild(errorElement); // Add the error message to the HTML document
  };
  <div id="result-container"></div>