Skip to content
Alon Zakai edited this page May 7, 2013 · 1 revision

As mentioned in CodeGuidlinesAndLimitations, function pointers in asm.js must be used in a coherent way, with each function called using it's correct type. Let's look at an example:

#include <stdio.h>

typedef void(*voidReturnType)(const char *);

void voidReturn(const char *message) {
    printf( "voidReturn: %s\n", message );
}

int intReturn(const char *message) {
    printf( "intReturn: %s\n", message );
    return 1;
}

void voidReturnNoParam() {
    printf( "voidReturnNoParam:\n" );
}

void callFunctions(const voidReturnType * funcs, size_t size) {
    size_t current = 0;
    while (current < size) {
        funcs[current]("hello world");
        current++;
    }
}

int main() {
    voidReturnType functionList[3];

    functionList[0] = voidReturn;
    functionList[1] = (voidReturnType)intReturn;         // This will break
    functionList[2] = (voidReturnType)voidReturnNoParam; // This will break

    callFunctions(functionList, 3);
}

This will compile just fine when compiled to machine code. You can try it by sving the code as main.c and executing: cc main.c and then ./a.out. You'll see this output:

voidReturn: hello world
intReturn: hello world
voidReturnNoParam:

However, this will break in Emscripten. You can compile this on your own by saving this code as main.c and executing: emcc -O2 -g -s ASM_JS=1 main.c -o main.html. Then load it into your browser. It will break. To see why, let's look at the html source. You should be able to find this section:

  var FUNCTION_TABLE_vi = [b0,b0,b0,b0,b0,b0,_voidReturn,b0];
  var FUNCTION_TABLE_ii = [b1,b1,_intReturn,b1,b1,b1,b1,b1];
  var FUNCTION_TABLE_iii = [b2,b2,b2,b2,b2,b2,b2,b2];
  var FUNCTION_TABLE_v = [b3,b3,b3,b3,_voidReturnNoParam,b3,b3,b3];

Note: Because we are compiling using -g the function names aren't mangled and we can see there are different function tables categorized by the signature of the function.

When Emscripten-generated code executes a function, it will look up the function in the table based on its signature and execute it. In our callFunctions() method we are passed a list of functions of the signature vi and for that reason, FUNCTION_TABLE_vi is the table used to find them. It doesn't matter that in main() we've added compatible functions to functionList. They will not be found because their signature is different.

There are two ways to fix this.

The first is to cast the function pointer back to its original signature just before calling it.

void callFunctions(const voidReturnType * funcs, size_t size) {
    size_t current = 0;
    while (current < size) {
        if ( current == 1 ) {
            ((intReturnType)funcs[current])("hello world"); // Special-case cast
        } else {
            funcs[current]("hello world");
        }
        current++;
    }
}

This requires the receiver of the table to have special knowledge about what is in the list (you can see this in the special case for index 1 in the while loop). Additionally, emcc will continue to complain about the original cast taking place in main() when adding the function to functionList[1].

A second (and better) solution is to make an adapter function which calls the original function and to place the adapter in the list.

void voidReturnNoParamAdapter(const char *message) {
    voidReturnNoParam();
}

int main() {
    voidReturnType functionList[3];

    functionList[0] = voidReturn;
    functionList[1] = (voidReturnType)intReturn; // Fixed in callFunctions
    functionList[2] = voidReturnNoParamAdapter; // Fixed by Adapter

    callFunctions(functionList, 3);
}

Here, we've made a function voidReturnNoParamAdapter() and added that to functionList[2].