-1

I've been stuck on this for a while now and I cannot seem to find good resources to my problem. I am coming from and "only C" background, so most of the web dev stuff is completely new for me.

I wrote a C function float editDistance(char *str1, char *str2) that returns the edit distance of 2 char arrays. Right now the goal is to successfully call this function from a JS environment.

After ensuring that the code works with the recommended Emscipten ccall method, I decided to move on. Now I use Emscripten to compile the C code with flags -O3, -s WASM=1, -s EXPORTED_FUNCTIONS="['_editDistance']", and -s SIDE_MODULE=1 -s to Wasm. The JS code I'm trying to wrap around my WebAssembly is:

// Allocate memory for the wasm module to run in. (65536*256 bit)
let wasmMemory = new WebAssembly.Memory({
    initial: 256
});

let info = {
    env: {
        abort: function() {},
        memoryBase: 0,
        tableBase: 0,
        memory: wasmMemory,
        table: new WebAssembly.Table({initial: 2, element: 'anyfunc'}),
    }
}

// Define the strings
let str1 = "abcd";
let str2 = "abcd";

// Allocate memory on the wasm partition for the HEAPU8
let HEAPU8 = new Uint8Array(wasmMemory.buffer);

// Create the char arrays on the heap from the strings
let stackPtr = 0;
let str1Ptr = stackPtr;
stackPtr = stringToASCIIArray(str1, HEAPU8, stackPtr);

let str2Ptr = stackPtr;
stackPtr = stringToASCIIArray(str2, HEAPU8, stackPtr);

// Read the wasm file and instantiate it with the above environment setup. Then
// call the exported function with the string pointers.
let wasmBinaryFile = 'bin/edit_distanceW.wasm';
fetch(wasmBinaryFile, {credentials:"same-origin"})
    .then((response) => response.arrayBuffer())
    .then((binary) => WebAssembly.instantiate(binary,info))
    .then((wa) => alert(wa.instance.exports._editDistance(str1Ptr, str2Ptr)));

// Converts a string to an ASCII byte array on the specified memory
function stringToASCIIArray(str, outU8Array, idx){
    let length = str.length + 1;

    let i;
    for(i=0; i<length; i++){
        outU8Array[idx+i] = str.charCodeAt(i);
    }
    outU8Array[idx+i]=0;

    return (idx + length);
}

The generated wasm file when converted to wat demands these imports:

  (import "env" "abort" (func (;0;) (type 0)))
  (import "env" "memoryBase" (global (;0;) i32))
  (import "env" "tableBase" (global (;1;) i32))
  (import "env" "memory" (memory (;0;) 256))
  (import "env" "table" (table (;0;) 2 anyfunc))

.. and exports these:

  (export "__post_instantiate" (func 7))
  (export "_editDistance" (func 9))
  (export "runPostSets" (func 6))
  (elem (;0;) (get_global 1) 8 1))

Now, when I test the code the strings are passed to the C module without a problem. A few function calls are even made on them (strLen) before things go south. In the C function there is this nasty nested loop that does the main computation, iterating thru a 2D array while reading the characters from the strings (C code just been ported from a paper with an ugly pseudo code, so pardon me the variable names):

do{
    for(p=0; p<editDistance; p++){
        // Do stuff
    }
    // Do more stuff
    editDistance++;
} while(fkp[len2*2-len1][editDistance] != len1);

Before the function enters the for() loop, the module still has the strings on memory str1Ptr=0x00 and str2Ptr=0x05 with the correct length and content. On the contrary, immediately after entering the for() loop the memory gets overwritten by garbage (mostly 0s), corrupting the end result. I suspect some stack saving and restoration problems on the scope change, as the exact same code compiled to my PC using gcc works like a charm.

Any idea what setup I'm missing that hinders the correct completion of the C function?

davidanderle
  • 638
  • 6
  • 12

1 Answers1

3

If you are starting out you probably want to use the emscripten-generated JS glue. That is, don't use SIDE_MODULE=1 and instead output to a files calle .js. The emscripten compiler will then generate both a .js and a .wasm file. You can then include the .js file in your project and it will handle all the loading and setup for you.

If you try to load the wasm file yourself, you will need to do a lot of work to replicate the emscripten environment, which will require a lot of internal details of emscripten. Also, those internal details of subject to change when you update to the new version of emscripten so you are creating more work for yourself.

sbc100
  • 2,619
  • 14
  • 11
  • If you do want to continue down the rabbithole though, it looks like your problem is that you are settings memoryBase to 0 but also trying to use 0 for your string address. `memoryBase` tells the side module where to put its global data. Also you will will want to call __post_instantiate before anything else. – sbc100 Nov 18 '18 at 01:46
  • Thanks, I've already experimented with the emscripten generated code so I decided to move on. Your observation, however, on the memoryBase and __post_instantiate gives me an idea what to look into. – davidanderle Nov 18 '18 at 11:29