Skip to content

Instantly share code, notes, and snippets.

@evanw
Created January 28, 2014 20:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save evanw/8675508 to your computer and use it in GitHub Desktop.
Save evanw/8675508 to your computer and use it in GitHub Desktop.
Code generated by emlink is incorrect

Code generated by emlink is incorrect

This code demonstrates a problem with emlink and static initializers. It implements a basic type registry system but has different runtime semantics after compiling with emscripten following the static linking instructions.

After running make cpp_single:

cc -std=c++11 -fno-rtti -fno-exceptions main.cpp side.cpp -lc++ -o a.out
./a.out
allocating type id 0
register name int as type id 0
starting tests
all tests passed
rm a.out

After running make cpp_modules:

cc -std=c++11 -fno-rtti -fno-exceptions -c main.cpp -o main.o
cc -std=c++11 -fno-rtti -fno-exceptions -c side.cpp -o side.o
cc main.o side.o -lc++ -o a.out
./a.out
allocating type id 0
register name int as type id 0
starting tests
all tests passed
rm a.out

After running make js_simple:

LLVM=~/.emsdk_portable/clang/3.2_64bit/bin python ~/.emsdk_portable/emscripten/1.7.8/emcc -s ASM_JS=1 -O1 -std=c++11 -fno-rtti -fno-exceptions main.cpp side.cpp -o compiled.js
node compiled.js
allocating type id 0
register name int as type id 0
starting tests
all tests passed
rm compiled.js

After running make js_modules:

LLVM=~/.emsdk_portable/clang/3.2_64bit/bin python ~/.emsdk_portable/emscripten/1.7.8/emcc -s ASM_JS=1 -O1 -std=c++11 -fno-rtti -fno-exceptions main.cpp -s MAIN_MODULE=1 -o main.js
warning: Casting a function pointer type to a potentially incompatible one (use -s VERBOSE=1 to see more)
warning: See https://github.com/kripken/emscripten/wiki/CodeGuidelinesAndLimitations#function-pointer-issues for more information on dangerous function pointer casts
warning: Incompatible function pointer casts are very dangerous with ASM_JS=1, you should investigate and correct these
LLVM=~/.emsdk_portable/clang/3.2_64bit/bin python ~/.emsdk_portable/emscripten/1.7.8/emcc -s ASM_JS=1 -O1 -std=c++11 -fno-rtti -fno-exceptions side.cpp -s SIDE_MODULE=1 -o side.js
LLVM=~/.emsdk_portable/clang/3.2_64bit/bin python ~/.emsdk_portable/emscripten/1.7.8/emlink.py main.js side.js compiled.js
Main module: main.js
Side module: side.js
Output: compiled.js
node compiled.js
allocating type id 0
register name int as type id 0
starting tests
allocating type id 1
could not find name for type with id 1

compiled.js:183832
      throw e;
            ^
Assertion failed: TypeID::toName(TypeID::value<int>()) == std::string("int"), at: side.cpp,6,main at Error
    at stackTrace (compiled.js:932:15)
    at ___assert_fail (compiled.js:4623:210)
    at Object._main (compiled.js:181880:3)
    at Object.callMain (compiled.js:183815:30)
    at doRun (compiled.js:183855:25)
    at run (compiled.js:183868:5)
    at Object.<anonymous> (compiled.js:183911:1)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
make: *** [js_modules] Error 8

Static linking causes warnings about incompatible function pointer casts, but adding -s VERBOSE=1 reveals that the warnings are for functions that are never called as far as I can tell:

warning: Casting potentially incompatible function pointer void (i32, i8*, i32*)* to i32 (i32, i8*, i32*)*, for _verr
warning: See https://github.com/kripken/emscripten/wiki/CodeGuidelinesAndLimitations#function-pointer-issues for more information on dangerous function pointer casts
warning: Incompatible function pointer casts are very dangerous with ASM_JS=1, you should investigate and correct these
warning: Casting potentially incompatible function pointer void (i32, i8*, i32*)* to i32 (i32, i8*, i32*)*, for _verrx
#pragma once
#include <string>
#include <type_traits>
namespace detail {
int nextTypeID();
int typeIDForName(const std::string &name);
const char *nameForTypeID(int typeID);
void registerTypeID(const char *name, int typeID);
template <typename T>
struct Value {
static int value() {
static int value = nextTypeID();
return value;
}
private:
Value();
};
}
struct TypeID {
static const char *toName(int typeID) { return std::move(detail::nameForTypeID(typeID)); }
static int fromName(const std::string &name) { return detail::typeIDForName(name); }
template <typename T>
static int registerName(const char *name) { detail::registerTypeID(name, value<T>()); return 0; }
template <typename T>
static int value() { return detail::Value<typename std::remove_cv<T>::type>::value(); }
private:
TypeID();
};
#include "common.h"
#include <assert.h>
#include <unordered_map>
static int _ = TypeID::registerName<int>("int");
static std::unordered_map<std::string, int> &nameToTypeID() {
static std::unordered_map<std::string, int> result;
return result;
}
static std::unordered_map<int, const char *> &typeIDToName() {
static std::unordered_map<int, const char *> result;
return result;
}
int detail::nextTypeID() {
static int nextID;
printf("allocating type id %d\n", nextID);
return nextID++;
}
int detail::typeIDForName(const std::string &name) {
auto &map = nameToTypeID();
auto it = map.find(name);
if (it == map.end()) {
printf("could not find type id for type %s\n", name.c_str());
return TypeID::value<void>();
}
return it->second;
}
const char *detail::nameForTypeID(int typeID) {
auto &map = typeIDToName();
auto it = map.find(typeID);
if (it == map.end()) {
printf("could not find name for type with id %d\n", typeID);
return "?";
}
return it->second;
}
void detail::registerTypeID(const char *name, int typeID) {
printf("register name %s as type id %d\n", name, typeID);
assert(nameToTypeID().count(name) == 0); // Prevent registering a type name twice
assert(typeIDToName().count(typeID) == 0); // Prevent registering a type id twice
nameToTypeID()[name] = typeID;
typeIDToName()[typeID] = name;
assert(nameToTypeID()[name] == typeID);
assert(typeIDToName()[typeID] == name);
}
SDK = ~/.emsdk_portable
JS_FLAGS = -s ASM_JS=1 -O1
CPP_FLAGS = -std=c++11 -fno-rtti -fno-exceptions
EMSCRIPTEN_PATH = $(shell $(SDK)/emsdk active_path emscripten-1.7.8)
CLANG_PATH = $(shell dirname $(shell PATH=$(shell $(SDK)/emsdk active_path clang-3.2-64bit):$(PATH) which clang))
EMCC = LLVM=$(CLANG_PATH) python $(shell PATH=$(EMSCRIPTEN_PATH):$(PATH) which emcc)
EMLINK = LLVM=$(CLANG_PATH) python $(shell dirname $(shell PATH=$(EMSCRIPTEN_PATH):$(PATH) which emcc))/emlink.py
default: cpp_simple cpp_modules js_simple js_modules
cpp_simple:
cc $(CPP_FLAGS) main.cpp side.cpp -lc++ -o a.out
./a.out
rm a.out
cpp_modules:
cc $(CPP_FLAGS) -c main.cpp -o main.o
cc $(CPP_FLAGS) -c side.cpp -o side.o
cc main.o side.o -lc++ -o a.out
./a.out
rm a.out
js_simple:
$(EMCC) $(JS_FLAGS) $(CPP_FLAGS) main.cpp side.cpp -o compiled.js
node compiled.js
rm compiled.js
js_modules:
$(EMCC) $(JS_FLAGS) $(CPP_FLAGS) main.cpp -s MAIN_MODULE=1 -o main.js
$(EMCC) $(JS_FLAGS) $(CPP_FLAGS) side.cpp -s SIDE_MODULE=1 -o side.js
$(EMLINK) main.js side.js compiled.js
node compiled.js
rm compiled.js
#include "common.h"
#include <assert.h>
int main() {
printf("starting tests\n");
assert(TypeID::toName(TypeID::value<int>()) == std::string("int"));
assert(TypeID::fromName("int") == TypeID::value<int>());
printf("all tests passed\n");
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment