Search code examples
javascriptarraysasm.js

How do I access arrays on the asm.js heap from Javascript code, when the array's type is not Uint8?


I'm trying to use emscripten and asm.js in order to speed up my Javascript code. I need to get data from an Int32Array into my compiled C function. According to this Github site I can allocate a buffer, copy data into it, and call a function taking that buffer's pointer as input, like so:

var buf = Module._malloc(myTypedArray.length*myTypedArray.BYTES_PER_ELEMENT);
Module.HEAPU8.set(myTypedArray, buf);
Module.ccall('my_function', 'number', ['number'], [buf]);
Module._free(buf);

But it doesn't work for anything besides an Uint8Array, because Uint8Array.set "helpfully" converts the input array's data type to Uint8 instead of just doing a raw copy. In other words, if I try to copy Int32Array.of(1, -1) into the heap at address 100 using this method, I'll get

{ ... 100:1, 101:255, 102:0, 103:0, 104:0, 105:0, 106:0, 107:0 ... }

instead of

{ ... 100:1, 101:0, 102:0, 103:0, 104:255, 105:255, 106:255, 107:255 ... }

(assuming little endian)

So how am I supposed to copy the data to and from the asm.js heap? I understand that an ArrayBuffer object can be bitwise-casted to any typed array type, but it doesn't seem possible to do the reverse (correction: see Jaromanda X's comment). Also, I read, considered, and rejected the website's suggestion to prefer setValue / getValue whenever possible because I have millions of things to copy and I'd like to avoid the overhead of one function call each if at all possible.


Solution

  • So it turns out I was wrong: it is possible to convert Int32Array to an ArrayBuffer and consequently to an Uint8Array view of the original array. Here's a complete example of a function that sums up an array of int32_t implemented as a JS function passing an array of int32_t to a C function:

    sum.c (compiled to sum.c.js)

    #include <stdint.h>
    #include <stddef.h>
    
    double sum_i32(const int32_t* ints, size_t count) {
        double result = 0.0;
        while (count-- > 0) {
            result += *ints++;
        }
        return result;
    }
    

    sum.js

    "use strict";
    
    const cModule = require("./sum.c.js");
    
    module.exports.sum_i32 = function sum_i32(array) {
        // Convert to array of int32_t if needed.
        if (!(array instanceof Int32Array)) {
            array = new Int32Array(array);
        }
        // Allocate a buffer to store a copy of the input array.
        const ptr = cModule._malloc(array.length * 4);
        let result = NaN;
        if (ptr === 0) {
            throw "Out of memory";
        }
        try {
            // View int32_t array as uint8_t using the int array's ArrayBuffer.
            const u8view = new Uint8Array(array.buffer);
            // Copy it to the right position in the heap and call the C function.
            cModule.HEAPU8.set(u8view, ptr);
            result = cModule._sum_i32(ptr, array.length);
        } finally {
            cModule._free(ptr);
        }
        return result;
    }