diff options
-rw-r--r-- | src/jsifier.js | 5 | ||||
-rw-r--r-- | tests/runner.py | 53 | ||||
-rwxr-xr-x | tools/bindings_generator.py | 86 | ||||
-rw-r--r-- | tools/shared.py | 2 |
4 files changed, 131 insertions, 15 deletions
diff --git a/src/jsifier.js b/src/jsifier.js index 3c2340ce..aa1e3c60 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -237,6 +237,10 @@ function JSify(data, functionsOnly, givenFunctions, givenGlobalVariables) { constant = makePointer(constant, null, BUILD_AS_SHARED_LIB ? 'ALLOC_NORMAL' : 'ALLOC_STATIC', item.type); var js = item.ident + '=' + constant + ';'; + // Special case: class vtables. We make sure they are null-terminated, to allow easy runtime operations + if (item.ident.substr(0, 5) == '__ZTV') { + js += '\n' + makePointer('[0]', null, BUILD_AS_SHARED_LIB ? 'ALLOC_NORMAL' : 'ALLOC_STATIC', ['void*']) + ';'; + } if (item.ident in EXPORTED_GLOBALS) { js += '\nModule["' + item.ident + '"] = ' + item.ident + ';'; } @@ -903,6 +907,7 @@ function JSify(data, functionsOnly, givenFunctions, givenGlobalVariables) { var preFile = BUILD_AS_SHARED_LIB ? 'preamble_sharedlib.js' : 'preamble.js'; var pre = processMacros(preprocess(read(preFile).replace('{{RUNTIME}}', getRuntime()), CONSTANTS)); print(pre); + print('Runtime.QUANTUM_SIZE = ' + QUANTUM_SIZE); if (RUNTIME_TYPE_INFO) { Types.cleanForRuntime(); print('Runtime.typeInfo = ' + JSON.stringify(Types.types)); diff --git a/tests/runner.py b/tests/runner.py index 3ee80826..04ebaa19 100644 --- a/tests/runner.py +++ b/tests/runner.py @@ -5,7 +5,7 @@ See settings.py file for options¶ms. Edit as needed. ''' from subprocess import Popen, PIPE, STDOUT -import os, unittest, tempfile, shutil, time, inspect, sys, math, glob, tempfile, re, json +import os, unittest, tempfile, shutil, time, inspect, sys, math, glob, tempfile, re, json, difflib # Setup @@ -229,13 +229,19 @@ class RunnerCore(unittest.TestCase): if type(value) is not str: value = value() # lazy loading if type(string) is not str: string = string() if value not in string: - raise Exception("Expected to find '%s' in '%s'" % (limit_size(value), limit_size(string))) + raise Exception("Expected to find '%s' in '%s', diff:\n\n%s" % ( + limit_size(value), limit_size(string), + limit_size(''.join([a.rstrip()+'\n' for a in difflib.unified_diff(value.split('\n'), string.split('\n'), fromfile='expected', tofile='actual')])) + )) def assertNotContained(self, value, string): if type(value) is not str: value = value() # lazy loading if type(string) is not str: string = string() if value in string: - raise Exception("Expected to NOT find '%s' in '%s'" % (limit_size(value), limit_size(string))) + raise Exception("Expected to NOT find '%s' in '%s', diff:\n\n%s" % ( + limit_size(value), limit_size(string), + limit_size(''.join([a.rstrip()+'\n' for a in difflib.unified_diff(value.split('\n'), string.split('\n'), fromfile='expected', tofile='actual')])) + )) ################################################################################################### @@ -3452,6 +3458,10 @@ if 'benchmark' not in str(sys.argv): Child2() : Parent(9) { printf("Child2:%d\\n", value); }; int getValCube() { return value*value*value; } static void printStatic() { printf("*static*\\n"); } + + virtual void virtualFunc() { printf("*virtualf*\\n"); } + virtual void virtualFunc2() { printf("*virtualf2*\\n"); } + static void runVirtualFunc(Child2 *self) { self->virtualFunc(); }; private: void doSomethingSecret() { printf("security breached!\\n"); }; // we should not be able to do this }; @@ -3527,6 +3537,32 @@ if 'benchmark' not in str(sys.argv): Child2.prototype.printStatic(); // static calls go through the prototype + // virtual function + c2.virtualFunc(); + Child2.prototype.runVirtualFunc(c2); + c2.virtualFunc2(); + + // extend the class from JS + var c3 = new Child2; + customizeVTable(c3, [{ + original: Child2.prototype.virtualFunc, + replacement: function() { + print('*js virtualf replacement*'); + } + }, { + original: Child2.prototype.virtualFunc2, + replacement: function() { + print('*js virtualf2 replacement*'); + } + }]); + c3.virtualFunc(); + Child2.prototype.runVirtualFunc(c3); + c3.virtualFunc2(); + + c2.virtualFunc(); // original should remain the same + Child2.prototype.runVirtualFunc(c2); + c2.virtualFunc2(); + print('*ok*'); ''' @@ -3565,6 +3601,17 @@ Child2:9 0 1 *static* +*virtualf* +*virtualf* +*virtualf2* +Parent:9 +Child2:9 +*js virtualf replacement* +*js virtualf replacement* +*js virtualf2 replacement* +*virtualf* +*virtualf* +*virtualf2* *ok* ''', post_build=post2) diff --git a/tools/bindings_generator.py b/tools/bindings_generator.py index 66462b6d..87359fa0 100755 --- a/tools/bindings_generator.py +++ b/tools/bindings_generator.py @@ -40,6 +40,9 @@ It's only purpose is to make it easy to access the C++ code in the JS bindings, and to prevent DFE from removing the code we care about. The JS bindings do more serious work, creating class structures in JS and linking them to the C bindings. + +NOTE: ammo.js is currently the biggest consumer of this code. For some + more docs you can see that project's README ''' import os, sys, glob, re @@ -105,6 +108,8 @@ for classname, clazz in parsed.classes.iteritems(): struct['name'] = sname # Missing in CppHeaderParser print 'zz seen struct %s in %s' % (sname, classname) +print 'zz parents: ', parents + for classname, clazz in classes.iteritems(): # Various precalculations print 'zz precalc', classname @@ -177,6 +182,9 @@ for classname, clazz in classes.iteritems(): 1/0. # Fill in some missing stuff + method['returns_text'] = method['returns_text'].replace('&', '').replace('*', '') + if method['returns_text'] in parents: + method['returns_text'] = parents[method['returns_text']] + '::' + method['returns_text'] if method.get('returns_const'): method['returns_text'] = 'const ' + method['returns_text'] if method.get('returns_pointer'): while method['returns_text'].count('*') < method['returns_pointer']: @@ -355,10 +363,9 @@ gen_js = open(basename + '.js', 'w') gen_c.write('extern "C" {\n') -# Use this when calling a binding function when you want to pass a null pointer. -# Having this object saves us needing to do checks for the object being null each time in the bindings code. gen_js.write(''' // Bindings utilities + var Object__cache = {}; // we do it this way so we do not modify |Object| function wrapPointer(ptr, __class__) { var cache = __class__ ? __class__.prototype.__cache__ : Object__cache; @@ -370,14 +377,14 @@ function wrapPointer(ptr, __class__) { ret.__class__ = __class__; return cache[ptr] = ret; } -this['wrapPointer'] = wrapPointer; +Module['wrapPointer'] = wrapPointer; function castObject(obj, __class__) { return wrapPointer(obj.ptr, __class__); } -this['castObject'] = castObject; +Module['castObject'] = castObject; -this['NULL'] = wrapPointer(0); +Module['NULL'] = wrapPointer(0); function destroy(obj) { if (!obj['__destroy__']) throw 'Error: Cannot destroy object. (Did you create it yourself?)'; @@ -389,22 +396,77 @@ function destroy(obj) { delete Object__cache[obj.ptr]; } } -this['destroy'] = destroy; +Module['destroy'] = destroy; function compare(obj1, obj2) { return obj1.ptr === obj2.ptr; } -this['compare'] = compare; +Module['compare'] = compare; function getPointer(obj) { return obj.ptr; } -this['getPointer'] = getPointer; +Module['getPointer'] = getPointer; function getClass(obj) { return obj.__class__; } -this['getClass'] = getClass; +Module['getClass'] = getClass; + +function customizeVTable(object, replacementPairs) { + // Does not handle multiple inheritance + + // Find out vtable size + var vTable = getValue(object.ptr, 'void*'); + // This assumes our modification where we null-terminate vtables + var size = 0; + while (getValue(vTable + Runtime.QUANTUM_SIZE*size, 'void*')) { + size++; + } + + // Prepare replacement lookup table and add replacements to FUNCTION_TABLE + // There is actually no good way to do this! So we do the following hack: + // We create a fake vtable with canary functions, to detect which actual + // function is being called + var vTable2 = _malloc(size*Runtime.QUANTUM_SIZE); + setValue(object.ptr, vTable2, 'void*'); + var canaryValue; + var functions = FUNCTION_TABLE.length; + for (var i = 0; i < size; i++) { + var index = FUNCTION_TABLE.length; + (function(j) { + FUNCTION_TABLE.push(function() { + canaryValue = j; + }); + })(i); + FUNCTION_TABLE.push(0); + setValue(vTable2 + Runtime.QUANTUM_SIZE*i, index, 'void*'); + } + replacementPairs.forEach(function(pair) { + pair.original.call(object); + pair.originalIndex = getValue(vTable + canaryValue*Runtime.QUANTUM_SIZE, 'void*'); + }); + FUNCTION_TABLE = FUNCTION_TABLE.slice(0, functions); + + // Do the replacements + + var replacements = {}; + replacementPairs.forEach(function(pair) { + var replacementIndex = FUNCTION_TABLE.length; + FUNCTION_TABLE.push(pair.replacement); + FUNCTION_TABLE.push(0); + replacements[pair.originalIndex] = replacementIndex; + }); + + // Copy and modify vtable + for (var i = 0; i < size; i++) { + var value = getValue(vTable + Runtime.QUANTUM_SIZE*i, 'void*'); + if (value in replacements) value = replacements[value]; + setValue(vTable2 + Runtime.QUANTUM_SIZE*i, value, 'void*'); + } + return object; +} +Module['customizeVTable'] = customizeVTable; ''') def generate_wrapping_code(classname): @@ -478,6 +540,8 @@ def generate_class(generating_classname, classname, clazz): # TODO: deprecate ge return ([] if not need_self else [classname + ' * self']) + map(lambda i: args[i]['type'] + ' arg' + str(i), range(len(args))) def justargs(args): return map(lambda i: 'arg' + str(i), range(len(args))) + def justtypes(args): # note: this ignores 'self' + return map(lambda i: args[i]['type'], range(len(args))) fullname = ('emscripten_bind_' + generating_classname + '__' + mname).replace('::', '__') generating_classname_suffixed = generating_classname @@ -517,8 +581,7 @@ def generate_class(generating_classname, classname, clazz): # TODO: deprecate ge gen_c.write(method['operator']) else: # normal method gen_c.write(''' %s%s%s(%s);''' % ('return ' if ret.replace(' ', '') != 'void' else '', - callprefix, actualmname, ', '.join(justargs(args)[:i]))) - + callprefix, actualmname, ', '.join(justargs(args)[:i]))) gen_c.write('\n') gen_c.write('}') else: @@ -690,6 +753,7 @@ struct EmscriptenEnsurer { EmscriptenEnsurer() { // Actually use the binding functions, so DFE will not eliminate them + // FIXME: A negative side effect of this is that they take up space in FUNCTION_TABLE int sum = 0; void *seen = (void*)%s; ''' % c_funcs[0]) diff --git a/tools/shared.py b/tools/shared.py index 28923eee..cb0d0691 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -70,7 +70,7 @@ def line_splitter(data): def limit_size(string, MAX=80*20): if len(string) < MAX: return string - return string[0:MAX] + '...' + return string[0:MAX/2] + '\n[..]\n' + string[-MAX/2:] def pick_llvm_opts(optimization_level, optimize_size, allow_nonportable=False, quantum_size=4, use_aa=False): opts = [] |