diff options
-rw-r--r-- | ChangeLog | 8 | ||||
-rw-r--r-- | docs/paper.pdf | bin | 204696 -> 220464 bytes | |||
-rw-r--r-- | docs/paper.tex | 73 | ||||
-rwxr-xr-x | emscripten.py | 29 | ||||
-rw-r--r-- | src/library.js | 48 | ||||
-rw-r--r-- | src/preamble.js | 8 | ||||
-rw-r--r-- | tests/autoassemble.bc | bin | 576 -> 0 bytes | |||
-rw-r--r-- | tests/autoassemble.c | 6 | ||||
-rw-r--r-- | tests/cases/unannotated.ll (renamed from tests/unannotated.ll) | 0 | ||||
-rw-r--r-- | tests/cases/unannotated.txt | 1 | ||||
-rw-r--r-- | tests/files.cpp | 13 | ||||
-rw-r--r-- | tests/runner.py | 32 | ||||
-rwxr-xr-x[-rw-r--r--] | tools/bindings_generator.py | 99 | ||||
-rw-r--r-- | tools/shared.py | 6 |
14 files changed, 260 insertions, 63 deletions
@@ -1,3 +1,11 @@ +2011-07-10: Version 1.4 + + * Compiling and loading of dynamic libraries + * Automatic bindings generation using CppHeaderParser + * Much improved emscripten.py + * Many library improvements and fixes + * Various bug fixes + 2011-06-22: Version 1.3 * Optional typed arrays with single shared buffer (TA2) diff --git a/docs/paper.pdf b/docs/paper.pdf Binary files differindex 9cc9f207..755eaddc 100644 --- a/docs/paper.pdf +++ b/docs/paper.pdf diff --git a/docs/paper.tex b/docs/paper.tex index 4bb8b267..5687788f 100644 --- a/docs/paper.tex +++ b/docs/paper.tex @@ -499,9 +499,24 @@ more challenging. For example, if we want to convert exact same behavior, in particular, we must handle overflows properly, which would not be the case if we just implement this as $\%1 + \%2$ in JavaScript. For example, with inputs of $255$ and $1$, the correct output is 0, but simple addition in JavaScript will give us 256. We -can emulate the proper behavior by adding additional code, one way -(not necessarily the most optimal) would be to check for overflows after -each addition, and correct them as necessary. This however significantly degrades performance. +can of course emulate the proper behavior by adding additional code. +This however significantly degrades performance, +because modern JavaScript engines can often translate something like $z = x + y$ into +native code containing a single instruction (or very close to that), but if instead we had +something like $z = (x + y)\&255$ (in order to correct overflows), the JavaScript engine +would need to generate additional code to perform the AND operation.\footnote{ +In theory, the JavaScript engine could determine that we are implicitly working +on 8-bit values here, and generate machine code that no longer needs the AND operation. +However, most or all modern JavaScript engines have just two internal numeric types, doubles and +32-bit integers. This is so because they are tuned for `normal' JavaScript code +on the web, which in most cases is served well by just those two types. + +In addition, even if JavaScript engines did analyze code containing $\&255$, etc., +in order to deduce that a variable can be implemented +as an 8-bit integer, there is a cost to including all the necessary $\&255$ text +in the script, because code size is a significant factor on the web. Adding even +a few characters for every single mathematic operation, in a large JavaScript file, +could add up to a significant increase in download size.} Emscripten's approach to this problem is to allow the generation of both accurate code, that is identical in behavior to LLVM assembly, and inaccurate code which is @@ -683,6 +698,51 @@ improve the speed as well, as are improvements to LLVM, the Closure Compiler, and JavaScript engines themselves; see further discussion in the Summary. +\subsection{Limitations} + +Emscripten's compilation approach, as has been described in this Section so far, +is to generate `natural' JavaScript, as close as possible to normal JavaScript +on the web, so that modern JavaScript engines perform well on it. In particular, +we try to generate `normal' JavaScript operations, like regular addition and +multiplication and so forth. This is a very +different approach than, say, emulating a CPU on a low level, or for the case +of LLVM, writing an LLVM bitcode interpreter in JavaScript. The latter approach +has the benefit of being able to run virtually any compiled code, at the cost +of speed, whereas Emscripten makes a tradeoff in the other direction. We will +now give a summary of some of the limitations of Emscripten's approach. + +\begin{itemize} +\item \textbf{64-bit Integers}: JavaScript numbers are all 64-bit doubles, with engines + typically implementing them as 32-bit integers where possible for speed. + A consequence of this is that it is impossible to directly implement + 64-bit integers in JavaScript, as integer values larger than 32 bits will become doubles, + with only 53 bits for the significand. Thus, when Emscripten uses normal + JavaScript addition and so forth for 64-bit integers, it runs the risk of + rounding effects. This could be solved by emulating 64-bit integers, + but it would be much slower than native code. +\item \textbf{Multithreading}: JavaScript has Web Workers, which are additional + threads (or processes) that communicate via message passing. There is no + shared state in this model, which means that it is not directly possible + to compile multithreaded code in C++ into JavaScript. A partial solution + could be to emulate threads, without Workers, by manually controlling + which blocks of code run (a variation on the switch in a loop construction + mentioned earlier) and manually switching between threads every so often. + However, in that case there would not be any utilization + of additional CPU cores, and furthermore performance would be slow due to not + using normal JavaScript loops. +\end{itemize} + +After seeing these limitations, it is worth noting that some advanced LLVM instructions turn out to be +surprisingly easy to implement. For example, C++ exceptions are represented in +LLVM by \emph{invoke} and \emph{unwind}, where \emph{invoke} is a call to a function that will +potentially trigger an \emph{unwind}, and \emph{unwind} returns to the earliest invoke. +If one were to implement those +in a typical compiler, doing so would require careful work. In Emscripen, however, +it is possible to do so using JavaScript exceptions in a straightforward manner: +\emph{invoke} becomes a function call wrapped in a \emph{try} block, and \emph{unwind} +becomes \emph{throw}. This is a case where compiling to a high-level language turns +out to be quite convenient. + \section{Emscripten's Architecture} \label{sec:emarch} @@ -997,12 +1057,7 @@ things, compile real-world C and C++ code and run that on the web. In addition, by compiling the runtimes of languages which are implemented in C and C++, we can run them on the web as well, for example Python and Lua. -Important future tasks for Emscripten are to broaden its -standard library and to support additional types of code, such as -multithreaded code (JavaScript Web Workers do not support shared state, -so this is not possible directly, but it can be emulated in various ways). - -But perhaps the largest future goal of Emscripten is to improve the performance of +Perhaps the largest future goal of Emscripten is to improve the performance of the generated code. As we have seen, speeds of around $1/10$th that of GCC are possible, which is already good enough for many purposes, but can be improved much more. The code Emscripten generates will become faster diff --git a/emscripten.py b/emscripten.py index 6477439c..6fe7b504 100755 --- a/emscripten.py +++ b/emscripten.py @@ -9,9 +9,10 @@ import tempfile import tools.shared as shared -# Temporary files that should be deleted once teh program is finished. +# Temporary files that should be deleted once the program is finished. TEMP_FILES_TO_CLEAN = [] -# The data layout used by llvm-gcc (as opposed to clang). +# The data layout used by llvm-gcc (as opposed to clang, which doesn't have the +# f128:128:128 part). GCC_DATA_LAYOUT = ('target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16' '-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64' '-v128:128:128-a0:0:64-f80:32:32-f128:128:128-n8:16:32"') @@ -42,9 +43,8 @@ def assemble(filepath): The path to the assembled file. """ if not filepath.endswith('.bc'): - out = get_temp_file('.bc') - ret = subprocess.call([shared.LLVM_AS, '-o=-', filepath], stdout=out) - out.close() + command = [shared.LLVM_AS, '-o=-', filepath] + with get_temp_file('.bc') as out: ret = subprocess.call(command, stdout=out) if ret != 0: raise RuntimeError('Could not assemble %s.' % filepath) filepath = out.name return filepath @@ -61,10 +61,8 @@ def disassemble(filepath): The path to the disassembled file. """ if not filepath.endswith('.ll'): - out = get_temp_file('.ll') command = [shared.LLVM_DIS, '-o=-', filepath] + shared.LLVM_DIS_OPTS - ret = subprocess.call(command, stdout=out) - out.close() + with get_temp_file('.ll') as out: ret = subprocess.call(command, stdout=out) if ret != 0: raise RuntimeError('Could not disassemble %s.' % filepath) filepath = out.name return filepath @@ -79,10 +77,8 @@ def optimize(filepath): Returns: The path to the optimized file. """ - out = get_temp_file('.bc') - opts = shared.pick_llvm_opts(3, True) - ret = subprocess.call([shared.LLVM_OPT, '-o=-', filepath] + opts, stdout=out) - out.close() + command = [shared.LLVM_OPT, '-o=-', filepath] + shared.pick_llvm_opts(3, True) + with get_temp_file('.bc') as out: ret = subprocess.call(command, stdout=out) if ret != 0: raise RuntimeError('Could not optimize %s.' % filepath) return out.name @@ -96,9 +92,8 @@ def link(*objects): Returns: The path to the linked file. """ - out = get_temp_file('.bc') - ret = subprocess.call([shared.LLVM_LINK] + list(objects), stdout=out) - out.close() + command = [shared.LLVM_LINK] + list(objects) + with get_temp_file('.bc') as out: ret = subprocess.call(command, stdout=out) if ret != 0: raise RuntimeError('Could not link %s.' % objects) return out.name @@ -113,11 +108,9 @@ def compile_malloc(compiler): The path to the compiled dlmalloc as an LLVM bitcode (.bc) file. """ src = path_from_root('src', 'dlmalloc.c') - out = get_temp_file('.bc') includes = '-I' + path_from_root('src', 'include') command = [compiler, '-c', '-g', '-emit-llvm', '-m32', '-o-', includes, src] - ret = subprocess.call(command, stdout=out) - out.close() + with get_temp_file('.bc') as out: ret = subprocess.call(command, stdout=out) if ret != 0: raise RuntimeError('Could not compile dlmalloc.') return out.name diff --git a/src/library.js b/src/library.js index fb14580d..c831c9bc 100644 --- a/src/library.js +++ b/src/library.js @@ -19,14 +19,14 @@ var Library = { // ========================================================================== _scanString: function() { - // Supports %x, %4x, %d.%d + // Supports %x, %4x, %d.%d, %s var str = Pointer_stringify(arguments[0]); var stri = 0; var fmt = Pointer_stringify(arguments[1]); var fmti = 0; var args = Array.prototype.slice.call(arguments, 2); var argsi = 0; - var read = 0; + var fields = 0; while (fmti < fmt.length) { if (fmt[fmti] === '%') { fmti++; @@ -37,19 +37,35 @@ var Library = { var curr = 0; while ((curr < max_ || isNaN(max_)) && stri+curr < str.length) { if ((type === 'd' && parseInt(str[stri+curr]) >= 0) || - (type === 'x' && parseInt(str[stri+curr].replace(/[a-fA-F]/, 5)) >= 0)) { + (type === 'x' && parseInt(str[stri+curr].replace(/[a-fA-F]/, 5)) >= 0) || + (type === 's')) { curr++; } else { break; } } - if (curr === 0) { print("FAIL"); break; } + if (curr === 0) return 0; // failure var text = str.substr(stri, curr); stri += curr; - var value = type === 'd' ? parseInt(text) : eval('0x' + text); - {{{ makeSetValue('args[argsi]', '0', 'value', 'i32') }}} + switch (type) { + case 'd': { + {{{ makeSetValue('args[argsi]', '0', 'parseInt(text)', 'i32') }}} + break; + } + case 'x': { + {{{ makeSetValue('args[argsi]', '0', 'eval("0x" + text)', 'i32') }}} + break; + } + case 's': { + var array = intArrayFromString(text); + for (var j = 0; j < array.length; j++) { + {{{ makeSetValue('args[argsi]', 'j', 'array[j]', 'i8') }}} + } + break; + } + } argsi++; - read++; + fields++; } else { // not '%' if (fmt[fmti] === str[stri]) { fmti++; @@ -59,9 +75,12 @@ var Library = { } } } - return read; // XXX Possibly we should return EOF (-1) sometimes + return { fields: fields, bytes: stri }; + }, + sscanf__deps: ['_scanString'], + sscanf: function() { + return __scanString.apply(null, arguments).fields; }, - sscanf: '_scanString', _formatString__deps: ['$STDIO', 'isdigit'], _formatString: function() { @@ -752,6 +771,17 @@ var Library = { return ptr; }, + fscanf__deps: ['_scanString'], + fscanf: function(stream, format) { + var f = STDIO.streams[stream]; + if (!f) + return -1; // EOF + arguments[0] = Pointer_make(f.data.slice(f.position).concat(0), 0, ALLOC_STACK, 'i8'); + var ret = __scanString.apply(null, arguments); + f.position += ret.bytes; + return ret.fields; + }, + // unix file IO, see http://rabbit.eng.miami.edu/info/functions/unixio.html open: function(filename, flags, mode) { diff --git a/src/preamble.js b/src/preamble.js index 5903a5be..151c399d 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -388,6 +388,14 @@ function Pointer_stringify(ptr) { return ret; } +function Array_stringify(array) { + var ret = ""; + for (var i = 0; i < array.length; i++) { + ret += String.fromCharCode(array[i]); + } + return ret; +} + // Memory management var PAGE_SIZE = 4096; diff --git a/tests/autoassemble.bc b/tests/autoassemble.bc Binary files differdeleted file mode 100644 index 27a668c8..00000000 --- a/tests/autoassemble.bc +++ /dev/null diff --git a/tests/autoassemble.c b/tests/autoassemble.c new file mode 100644 index 00000000..0619933c --- /dev/null +++ b/tests/autoassemble.c @@ -0,0 +1,6 @@ +#include <stdio.h> + +int main() { + puts("test\n"); + return 0; +} diff --git a/tests/unannotated.ll b/tests/cases/unannotated.ll index 96ce5468..96ce5468 100644 --- a/tests/unannotated.ll +++ b/tests/cases/unannotated.ll diff --git a/tests/cases/unannotated.txt b/tests/cases/unannotated.txt new file mode 100644 index 00000000..9daeafb9 --- /dev/null +++ b/tests/cases/unannotated.txt @@ -0,0 +1 @@ +test diff --git a/tests/files.cpp b/tests/files.cpp index 1167bbdf..6283c4f1 100644 --- a/tests/files.cpp +++ b/tests/files.cpp @@ -76,6 +76,19 @@ int main() fclose(other); + // fscanf + + outf = fopen("fscan.f", "w"); + fprintf(outf, "10 hello"); + fclose(outf); + + int number; + char text[100]; + inf = fopen("fscan.f", "r"); + num = fscanf(inf, "%d %s", &number, text); + fclose(inf); + printf("fscanfed: %d - %s\n", number, text); + return 0; } diff --git a/tests/runner.py b/tests/runner.py index e0b9594a..e21a74a5 100644 --- a/tests/runner.py +++ b/tests/runner.py @@ -29,7 +29,6 @@ NAMESPACER = path_from_root('tools', 'namespacer.py') EMMAKEN = path_from_root('tools', 'emmaken.py') AUTODEBUGGER = path_from_root('tools', 'autodebugger.py') DFE = path_from_root('tools', 'dead_function_eliminator.py') -BINDINGS_GENERATOR = path_from_root('tools', 'bindings_generator.py') # Global cache for tests (we have multiple TestCase instances; this object lets them share data) @@ -185,6 +184,7 @@ class RunnerCore(unittest.TestCase): # Detect compilation crashes and errors if compiler_output is not None and 'Traceback' in compiler_output and 'in test_' in compiler_output: print compiler_output; assert 0 + assert os.path.exists(filename + '.o.js'), 'Emscripten failed to generate .js: ' + str(compiler_output) if output_processor is not None: output_processor(open(filename + '.o.js').read()) @@ -2037,7 +2037,7 @@ if 'benchmark' not in sys.argv: other.close() src = open(path_from_root('tests', 'files.cpp'), 'r').read() - self.do_test(src, 'size: 7\ndata: 100,-56,50,25,10,77,123\ninput:hi there!\ntexto\ntexte\n5 : 10,30,20,11,88\nother=some data.\nseeked=me da.\nseeked=ata.\nseeked=ta.', post_build=post) + self.do_test(src, 'size: 7\ndata: 100,-56,50,25,10,77,123\ninput:hi there!\ntexto\ntexte\n5 : 10,30,20,11,88\nother=some data.\nseeked=me da.\nseeked=ata.\nseeked=ta.\nfscanfed: 10 - hello\n', post_build=post) ### 'Big' tests @@ -2582,7 +2582,7 @@ if 'benchmark' not in sys.argv: open(header_filename, 'w').write(header) basename = os.path.join(self.get_dir(), 'bindingtest') - output = Popen(['python', BINDINGS_GENERATOR, basename, header_filename], stdout=PIPE, stderr=STDOUT).communicate()[0] + output = Popen([BINDINGS_GENERATOR, basename, header_filename], stdout=PIPE, stderr=STDOUT).communicate()[0] assert 'Traceback' not in output, 'Failure in binding generation: ' + output src = ''' @@ -2784,15 +2784,25 @@ Child2:9 # This test *should* fail assert 'Assertion failed' in str(e), str(e) - def test_unannotated(self): - self.do_ll_test(path_from_root('tests', 'unannotated.ll'), 'test\n') - def test_autoassemble(self): - filename = os.path.join(self.get_dir(), 'src.bc') - shutil.copy(path_from_root('tests', 'autoassemble.bc'), filename) - self.do_emscripten(filename, append_ext=False) - shutil.copy(filename + '.o.js', os.path.join(self.get_dir(), 'src.cpp.o.js')) - self.do_test(None, 'test\n', no_build=True) + src = r''' + #include <stdio.h> + + int main() { + puts("test\n"); + return 0; + } + ''' + dirname = self.get_dir() + filename = os.path.join(dirname, 'src.cpp') + self.build(src, dirname, filename) + + new_filename = os.path.join(dirname, 'new.bc') + shutil.copy(filename + '.o', new_filename) + self.do_emscripten(new_filename, append_ext=False) + + shutil.copy(filename + '.o.js', os.path.join(self.get_dir(), 'new.cpp.o.js')) + self.do_test(None, 'test\n', basename='new.cpp', no_build=True) def test_dlmalloc_linked(self): src = open(path_from_root('tests', 'dlmalloc_test.c'), 'r').read() diff --git a/tools/bindings_generator.py b/tools/bindings_generator.py index 359244ad..ac8ddbc5 100644..100755 --- a/tools/bindings_generator.py +++ b/tools/bindings_generator.py @@ -1,8 +1,10 @@ +#!/usr/bin/env python + ''' Use CppHeaderParser to parse some C++ headers, and generate binding code for them. Usage: - bindings_generator.py BASENAME HEADER1 HEADER2 ... + bindings_generator.py BASENAME HEADER1 HEADER2 ... [-- "LAMBDA"] BASENAME is the name used for output files (with added suffixes). HEADER1 etc. are the C++ headers to parse @@ -17,6 +19,9 @@ We generate the following: * BASENAME.js: JavaScript bindings file, with generated JavaScript wrapper objects. This is a high-level wrapping, using native JS classes. + * LAMBDA: Optionally, provide the text of a lambda function here that will be + used to process the header files. This lets you manually tweak them. + The C bindings file is basically a tiny C wrapper around the C++ code. 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 @@ -24,7 +29,7 @@ JS bindings do more serious work, creating class structures in JS and linking them to the C bindings. ''' -import os, sys, glob +import os, sys, glob, re abspath = os.path.abspath(os.path.dirname(__file__)) def path_from_root(*pathelems): @@ -39,21 +44,35 @@ import CppHeaderParser basename = sys.argv[1] +processor = lambda line: line +if '--' in sys.argv: + index = sys.argv.index('--') + processor = eval(sys.argv[index+1]) + sys.argv = sys.argv[:index] + # First pass - read everything classes = {} +struct_parents = {} + +all_h_name = basename + '.all.h' +all_h = open(all_h_name, 'w') for header in sys.argv[2:]: - #[('tests', 'bullet', 'src', 'BulletCollision/CollisionDispatch/btCollisionWorld.h')]: - parsed = CppHeaderParser.CppHeader(header) - #header = CppHeaderParser.CppHeader(path_from_root('tests', 'bullet', 'src', 'btBulletDynamicsCommon.h')) - #print header.classes.keys() - #print dir(header.classes['btCollisionShape']) - #print header.classes['btCollisionWorld']['methods']['public'] - for cname, clazz in parsed.classes.iteritems(): - if len(clazz['methods']['public']) > 0: # Do not notice stub classes - print 'Seen', cname - classes[cname] = clazz + all_h.write('//// ' + header + '\n') + all_h.write(processor(open(header, 'r').read())) + +all_h.close() + +parsed = CppHeaderParser.CppHeader(all_h_name) +for cname, clazz in parsed.classes.iteritems(): + #print 'zz see', cname + if len(clazz['methods']['public']) > 0: # Do not notice stub classes + #print 'zz for real', cname, clazz._public_structs + classes[cname] = clazz + for sname, struct in clazz._public_structs.iteritems(): + struct_parents[sname] = cname + #print 'zz seen struct %s in %s' % (sname, cname) # Second pass - generate bindings # TODO: Bind virtual functions using dynamic binding in the C binding code @@ -69,17 +88,47 @@ def generate_class(generating_cname, cname, clazz): inherited = generating_cname != cname for method in clazz['methods']['public']: - print ' ', method['name'], method + if method['pure_virtual']: return # Nothing to generate for pure virtual classes + for method in clazz['methods']['public']: mname = method['name'] args = method['parameters'] constructor = mname == cname + destructor = method['destructor'] + if destructor: continue if constructor and inherited: continue + if method['pure_virtual']: continue + + skip = False + for i in range(len(args)): + #print 'zz arggggggg', cname, 'x', mname, 'x', args[i]['name'], 'x', args[i]['type'], 'x' + if args[i]['name'].replace(' ', '') == '': + args[i]['name'] = 'arg' + str(i+1) + elif args[i]['name'] == '&': + args[i]['name'] = 'arg' + str(i+1) + args[i]['type'] += '&' + + if '>' in args[i]['name']: + print 'WARNING: odd ">" in %s, skipping' % cname + skip = True + break + #print 'c1', struct_parents.keys() + if args[i]['type'][-1] == '&': + sname = args[i]['type'][:-1] + if sname[-1] == ' ': sname = sname[:-1] + if sname in struct_parents: + args[i]['type'] = struct_parents[sname] + '::' + sname + '&' + elif sname.replace('const ', '') in struct_parents: + sname = sname.replace('const ', '') + args[i]['type'] = 'const ' + struct_parents[sname] + '::' + sname + '&' + #print 'POST arggggggg', cname, 'x', mname, 'x', args[i]['name'], 'x', args[i]['type'] + if skip: + continue # C - ret = (cname + ' *') if constructor else method['rtnType'] + ret = ((cname + ' *') if constructor else method['rtnType']).replace('virtual ', '') callprefix = 'new ' if constructor else 'self->' typedargs = ', '.join( ([] if constructor else [cname + ' * self']) + map(lambda arg: arg['type'] + ' ' + arg['name'], args) ) justargs = ', '.join(map(lambda arg: arg['name'], args)) @@ -98,11 +147,27 @@ def generate_class(generating_cname, cname, clazz): if constructor: generating_cname_suffixed += suffix + actualmname = '' + if mname == '__operator___assignment_': + callprefix = '*self = ' + continue # TODO + elif mname == '__operator____imult__': + callprefix = '*self * ' + continue # TODO + elif mname == '__operator____iadd__': + callprefix = '*self + ' + continue # TODO + elif mname == '__operator____isub__': + callprefix = '*self - ' + continue # TODO + else: + actualmname = mname + gen_c.write(''' %s %s(%s) { - return %s%s(%s); + %s%s%s(%s); } -''' % (ret, fullname, typedargs, callprefix, mname, justargs)) +''' % (ret, fullname, typedargs, 'return ' if ret.replace(' ', '') != 'void' else '', callprefix, actualmname, justargs)) # JS @@ -135,6 +200,8 @@ for cname, clazz in classes.iteritems(): for parent in clazz['inherits']: generate_class(cname, parent['class'], classes[parent['class']]) + # TODO: Add a destructor + # Finish up funcs = funcs.keys() diff --git a/tools/shared.py b/tools/shared.py index 8cb602df..9ccc2105 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -1,6 +1,10 @@ import shutil, time, os from subprocess import Popen, PIPE, STDOUT +abspath = os.path.abspath(os.path.dirname(__file__)) +def path_from_root(*pathelems): + return os.path.join(os.path.sep, *(abspath.split(os.sep)[:-1] + list(pathelems))) + CONFIG_FILE = os.path.expanduser('~/.emscripten') if not os.path.exists(CONFIG_FILE): shutil.copy(path_from_root('settings.py'), CONFIG_FILE) @@ -17,6 +21,8 @@ LLVM_DIS_OPTS = ['-show-annotations'] # For LLVM 2.8+. For 2.7, you may need to LLVM_INTERPRETER=os.path.expanduser(os.path.join(LLVM_ROOT, 'lli')) LLVM_COMPILER=os.path.expanduser(os.path.join(LLVM_ROOT, 'llc')) +BINDINGS_GENERATOR = path_from_root('tools', 'bindings_generator.py') + # Engine tweaks if '-s' not in SPIDERMONKEY_ENGINE: |