From 386dc0fa02d54a8d91a0993259415f5713d5e781 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Sun, 20 Nov 2011 21:02:46 -0800 Subject: [PATCH] sketch of emcc --- em++.py | 17 ++++ emcc.py | 231 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/runner.py | 13 +++ 3 files changed, 261 insertions(+) create mode 100755 em++.py create mode 100755 emcc.py diff --git a/em++.py b/em++.py new file mode 100755 index 000000000..1663dd06e --- /dev/null +++ b/em++.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +''' +See emcc.py. This script forwards to there, noting that we want C++ and not C by default +''' + +import os, sys + +__rootpath__ = os.path.abspath(os.path.dirname(__file__)) +def path_from_root(*pathelems): + return os.path.join(__rootpath__, *pathelems) +exec(open(path_from_root('tools', 'shared.py'), 'r').read()) + +emmaken = path_from_root('tools', 'emmaken.py') +os.environ['EMMAKEN_CXX'] = '1' +exit(os.execvp('python', ['python', emmaken] + sys.argv[1:])) + diff --git a/emcc.py b/emcc.py new file mode 100755 index 000000000..2499e5d9a --- /dev/null +++ b/emcc.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python + +''' +emcc - compiler helper script +============================= + +emcc is a drop-in replacement for a compiler like gcc or clang. + +Tell your build system to use this instead of the compiler, linker, ar and +ranlib. All the normal build commands will be sent to this script, which +will proxy them to the appropriate LLVM build commands, in order to +generate proper code for Emscripten to later process. + +For example, compilation will be translated into calls to clang +with -emit-llvm, and linking will be translated into calls to llvm-link, +and so forth. + +emcc is only meant to *COMPILE* source code into LLVM bitcode. It does +not do optimizations (in fact, it will disable -Ox flags and warn you +about that). The reason is that doing such optimizations early can lead +to bitcode that Emscripten cannot process properly, or can process but +not fully optimize. You can (and probably should) still run LLVM +optimizations though, by telling emscripten.py to do so (or run LLVM +opt yourself, but be careful with the parameters you pass). + +Example uses: + + * For configure, instead of ./configure, cmake, etc., run emconfiguren.py + with that command as an argument, for example + + emconfiguren.py ./configure [options] + + emconfiguren.py is a tiny script that just sets some environment vars + as a convenience. The command just shown is equivalent to + + EMMAKEN_JUST_CONFIGURE=1 RANLIB=PATH/emcc.py AR=PATH/emcc.py CXX=PATH/em++.py CC=PATH/emcc.py ./configure [options] + + where PATH is the path to this file. + + EMMAKEN_JUST_CONFIGURE tells emcc that it is being run in ./configure, + so it should relay everything to gcc/g++. You should not define that when + running make, of course. + + * With CMake, the same command will work (with cmake instead of ./configure). You may also be + able to do the following in your CMakeLists.txt: + + SET(CMAKE_C_COMPILER "PATH/emcc.py") + SET(CMAKE_CXX_COMPILER "PATH/em++.py") + SET(CMAKE_LINKER "PATH/emcc.py") + SET(CMAKE_CXX_LINKER "PATH/emcc.py") + SET(CMAKE_C_LINK_EXECUTABLE "PATH/emcc.py") + SET(CMAKE_CXX_LINK_EXECUTABLE "PATH/emcc.py") + SET(CMAKE_AR "PATH/emcc.py") + SET(CMAKE_RANLIB "PATH/emcc.py") + + * For SCons the shared.py can be imported like so: + __file__ = str(Dir('#/project_path_to_emscripten/dummy/dummy')) + __rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) + def path_from_root(*pathelems): + return os.path.join(__rootpath__, *pathelems) + exec(open(path_from_root('tools', 'shared.py'), 'r').read()) + + For using the Emscripten compilers/linkers/etc. you can do: + env = Environment() + ... + env.Append(CCFLAGS = COMPILER_OPTS) + env.Replace(LINK = LLVM_LD) + env.Replace(LD = LLVM_LD) + TODO: Document all relevant setup changes + +After setting that up, run your build system normally. It should generate +LLVM instead of the normal output, and end up with .ll files that you can +give to Emscripten. Note that this tool doesn't run Emscripten itself. Note +also that you may need to do some manual fiddling later, for example to +link files that weren't linked, and them llvm-dis them. + +Note the appearance of em++.py instead of emcc.py +for the C++ compiler. This is needed for cases where we get +a C++ file with a C extension, in which case CMake can be told +to run g++ on it despite the .c extension, see + + https://github.com/kripken/emscripten/issues/6 + +(If a similar situation occurs with ./configure, you can do the same there too.) + +emcc can be influenced by a few environment variables: + + EMMAKEN_NO_SDK - Will tell emcc *not* to use the emscripten headers. Instead + your system headers will be used. + + EMMAKEN_COMPILER - The compiler to be used, if you don't want the default clang. + +''' + +import sys +import os +import subprocess + +################### XXX +print >> sys.stderr, '***This is a WORK IN PROGRESS***' +################### XXX + +print >> sys.stderr, 'emcc.py: ', ' '.join(sys.argv) + +__rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) +def path_from_root(*pathelems): + return os.path.join(__rootpath__, *pathelems) +exec(open(path_from_root('tools', 'shared.py'), 'r').read()) + +# If this is a configure-type thing, just do that +CONFIGURE_CONFIG = os.environ.get('EMMAKEN_JUST_CONFIGURE') +CMAKE_CONFIG = 'CMakeFiles/cmTryCompileExec.dir' in ' '.join(sys.argv)# or 'CMakeCCompilerId' in ' '.join(sys.argv) +if CONFIGURE_CONFIG or CMAKE_CONFIG: + compiler = 'g++' if 'CXXCompiler' in ' '.join(sys.argv) or os.environ.get('EMMAKEN_CXX') else 'gcc' + cmd = [compiler] + EMSDK_OPTS + sys.argv[1:] + print >> sys.stderr, 'emcc.py, just configuring: ', cmd + exit(os.execvp(compiler, cmd)) + +try: + #f=open('/dev/shm/tmp/waka.txt', 'a') + #f.write('Args: ' + ' '.join(sys.argv) + '\nCMake? ' + str(CMAKE_CONFIG) + '\n') + #f.close() + + if os.environ.get('EMMAKEN_COMPILER'): + CXX = os.environ['EMMAKEN_COMPILER'] + else: + CXX = CLANG + + CC = to_cc(CXX) + + # If we got here from a redirection through emmakenxx.py, then force a C++ compiler here + if os.environ.get('EMMAKEN_CXX'): + CC = CXX + + CC_ADDITIONAL_ARGS = COMPILER_OPTS # + ['-g']? + ALLOWED_LINK_ARGS = ['-f', '-help', '-o', '-print-after', '-print-after-all', '-print-before', + '-print-before-all', '-time-passes', '-v', '-verify-dom-info', '-version' ] + TWO_PART_DISALLOWED_LINK_ARGS = ['-L'] # Ignore thingsl like |-L .| + + EMMAKEN_CFLAGS = os.environ.get('EMMAKEN_CFLAGS') + if EMMAKEN_CFLAGS: CC_ADDITIONAL_ARGS += EMMAKEN_CFLAGS.split(' ') + + # ---------------- End configs ------------- + + if len(sys.argv) == 2 and 'conftest' not in ' '.join(sys.argv): # Avoid messing with configure, see below too + # ranlib + sys.exit(0) + if len(sys.argv) == 1 or sys.argv[1] in ['x', 't']: + # noop ar + sys.exit(0) + + use_cxx = True + use_linker = True + header = False # pre-compiled headers. We fake that by just copying the file + + opts = [] + files = [] + for i in range(1, len(sys.argv)): + arg = sys.argv[i] + if arg.startswith('-'): + opts.append(arg) + else: + files.append(arg) + if arg.endswith('.c'): + use_cxx = False + if arg.endswith(('.c', '.cc', '.cpp', '.dT')): + use_linker = False + if arg.endswith('.h') and sys.argv[i-1] != '-include': + header = True + use_linker = False + + if '--version' in opts: + use_linker = False + + if set(sys.argv[1]).issubset(set('-cruqs')): # ar + sys.argv = sys.argv[:1] + sys.argv[3:] + ['-o='+sys.argv[2]] + assert use_linker, 'Linker should be used in this case' + + if use_linker: + call = LLVM_LD + newargs = ['-disable-opt'] + found_o = False + i = 0 + while i < len(sys.argv)-1: + i += 1 + arg = sys.argv[i] + if found_o: + newargs.append('-o=%s' % arg) + found_o = False + continue + if arg.startswith('-'): + if arg == '-o': + found_o = True + continue + prefix = arg.split('=')[0] + if prefix in ALLOWED_LINK_ARGS: + newargs.append(arg) + if arg in TWO_PART_DISALLOWED_LINK_ARGS: + i += 1 + elif arg.endswith('.so'): + continue # .so's do not exist yet, in many cases + else: + # not option, so just append + newargs.append(arg) + elif not header: + call = CXX if use_cxx else CC + newargs = sys.argv[1:] + for i in range(len(newargs)): + if newargs[i].startswith('-O'): + print >> sys.stderr, 'emcc.py: WARNING: Optimization flags (-Ox) are ignored in emcc. Tell emscripten.py to do that, or run LLVM opt.' + newargs[i] = '' + newargs = [ arg for arg in newargs if arg is not '' ] + CC_ADDITIONAL_ARGS + newargs.append('-emit-llvm') + if not use_linker: + newargs.append('-c') + else: + print >> sys.stderr, 'Just copy.' + shutil.copy(sys.argv[-1], sys.argv[-2]) + exit(0) + + #f=open('/dev/shm/tmp/waka.txt', 'a') + #f.write('Calling: ' + ' '.join(newargs) + '\n\n') + #f.close() + + print >> sys.stderr, "Running:", call, ' '.join(newargs) + + os.execvp(call, [call] + newargs) +except Exception, e: + print 'Error in emcc.py. (Is the config file ~/.emscripten set up properly?) Error:', e + raise + diff --git a/tests/runner.py b/tests/runner.py index 0c0c78b4f..fb1c7b674 100644 --- a/tests/runner.py +++ b/tests/runner.py @@ -4454,6 +4454,19 @@ TT = %s del T # T is just a shape for the specific subclasses, we don't test it itself class other(RunnerCore): + def test_emcc(self): + pass + # emcc src.cpp ==> should give a .js file + # emcc -O0 src.cpp ==> same as without -O0 (i.e., assertions, etc.) + # emcc -O1 src.cpp ==> no assertions, basic optimizations, plus eliminator, but no reloop + # emcc -O2 src.cpp ==> plus reloop + # emcc -O3 src.cpp ==> plus closure compiler + # emcc -typed-arrays=x .. ==> should use typed arrays + # emcc -llvm-opts=x .. ==> pick level of LLVM optimizations (default is 0, to be safe) + # emcc src.cpp -c ==> should give a .bc file + # linking - TODO + # TODO: when ready, switch tools/shared building to use emcc over emmaken + def test_eliminator(self): input = open(path_from_root('tools', 'eliminator', 'eliminator-test.js')).read() expected = open(path_from_root('tools', 'eliminator', 'eliminator-test-output.js')).read()