diff options
-rw-r--r-- | AUTHORS | 1 | ||||
-rwxr-xr-x | emcc | 38 | ||||
-rw-r--r-- | src/jsifier.js | 17 | ||||
-rw-r--r-- | src/modules.js | 5 | ||||
-rw-r--r-- | src/settings.js | 2 | ||||
-rwxr-xr-x | tests/runner.py | 183 | ||||
-rwxr-xr-x | tools/split.py | 94 |
7 files changed, 338 insertions, 2 deletions
@@ -37,3 +37,4 @@ a license to everyone to use it as detailed in LICENSE.) * Benjamin Stover <benjamin.stover@gmail.com> * Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com> * Janus Troelsen <janus.troelsen@stud.tu-darmstadt.de> +* Lars Schneider <lars.schneider@autodesk.com> (copyright owned by Autodesk, Inc.)
\ No newline at end of file @@ -268,6 +268,27 @@ Options that are modified or new in %s include: will not minify the code (closure does that) + --split <size> Splits the resulting javascript file into pieces + to ease debugging. This option only works if + Javascript is generated (target -o <name>.js). + Files with function declarations must be loaded + before main file upon execution. + + Without "-g" option: + Creates files with function declarations up + to the given size with the suffix + "_functions.partxxx.js" and a main file with + the suffix ".js". + + With "-g" option: + Recreates the directory structure of the C + source files and stores function declarations + in their respective C files with the suffix + ".js". If such a file exceeds the given size, + files with the suffix ".partxxx.js" are created. + The main file resides in the base directory and + has the suffix ".js". + --ignore-dynamic-linking Normally emcc will treat dynamic linking like static linking, by linking in the code from the dynamic library. This fails if the same @@ -471,6 +492,7 @@ try: pre_js = '' post_js = '' minify_whitespace = None + split_js_file = None preload_files = [] embed_files = [] compression = None @@ -527,6 +549,11 @@ try: minify_whitespace = int(newargs[i+1]) newargs[i] = '' newargs[i+1] = '' + elif newargs[i].startswith('--split'): + check_bad_eq(newargs[i]) + split_js_file = int(newargs[i+1]) + newargs[i] = '' + newargs[i+1] = '' elif newargs[i].startswith('--embed-file'): check_bad_eq(newargs[i]) embed_files.append(newargs[i+1]) @@ -601,6 +628,9 @@ try: newargs[i+1] = '' newargs = [ arg for arg in newargs if arg is not '' ] + if split_js_file: + settings_changes.append("PRINT_SPLIT_FILE_MARKER=1") + # Find input files input_files = [] @@ -1071,8 +1101,12 @@ try: html.close() else: - # copy final JS to output - shutil.move(final, target) + if split_js_file: + from tools.split import split_javascript_file + split_javascript_file(final, unsuffixed(target), split_js_file) + else: + # copy final JS to output + shutil.move(final, target) finally: if not TEMP_DIR: diff --git a/src/jsifier.js b/src/jsifier.js index 92b39b7d..8021f8a1 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -522,6 +522,11 @@ function JSify(data, functionsOnly, givenFunctions) { func.JS += ' */\n'; } + if (PRINT_SPLIT_FILE_MARKER) { + func.JS += '\n//FUNCTION_BEGIN_MARKER\n' + var associatedSourceFile = "NO_SOURCE"; + } + func.JS += 'function ' + func.ident + '(' + paramIdents.join(', ') + ') {\n'; if (PROFILE) { @@ -572,6 +577,13 @@ function JSify(data, functionsOnly, givenFunctions) { if (EXECUTION_TIMEOUT > 0) { ret += indent + 'if (Date.now() - START_TIME >= ' + (EXECUTION_TIMEOUT*1000) + ') throw "Timed out!" + (new Error().stack);\n'; } + + if (PRINT_SPLIT_FILE_MARKER && Debugging.on && Debugging.getAssociatedSourceFile(line.lineNum)) { + // Overwrite the associated source file for every line. The last line should contain the source file associated to + // the return value/address of outer most block (the marked function). + associatedSourceFile = Debugging.getAssociatedSourceFile(line.lineNum); + } + // for special labels we care about (for phi), mark that we visited them return ret + label.lines.map(function(line) { return line.JS + (Debugging.on ? Debugging.getComment(line.lineNum) : '') }) .join('\n') @@ -653,6 +665,11 @@ function JSify(data, functionsOnly, givenFunctions) { func.JS += ' return' + (func.returnType !== 'void' ? ' null' : '') + ';\n'; } func.JS += '}\n'; + + if (PRINT_SPLIT_FILE_MARKER) { + func.JS += '\n//FUNCTION_END_MARKER_OF_SOURCE_FILE_' + associatedSourceFile + '\n'; + } + if (func.ident in EXPORTED_FUNCTIONS) { func.JS += 'Module["' + func.ident + '"] = ' + func.ident + ';'; } diff --git a/src/modules.js b/src/modules.js index 0f3b483b..cf1b072e 100644 --- a/src/modules.js +++ b/src/modules.js @@ -127,6 +127,11 @@ var Debugging = { this.llvmLineToSourceFile[lineNum] + '"' : ''; }, + getAssociatedSourceFile: function(lineNum) { + if (!this.on) return null; + return lineNum in this.llvmLineToSourceLine ? this.llvmLineToSourceFile[lineNum] : null; + }, + getIdentifier: function(lineNum) { if (!this.on) return null; if (lineNum === undefined) { diff --git a/src/settings.js b/src/settings.js index f8a81711..9a1afd69 100644 --- a/src/settings.js +++ b/src/settings.js @@ -202,6 +202,8 @@ var SHELL_FILE = 0; // set this to a string to override the shell file used var SHOW_LABELS = 0; // Show labels in the generated code +var PRINT_SPLIT_FILE_MARKER = 0; // Prints markers in Javascript generation to split the file later on. See emcc --split option. + var BUILD_AS_SHARED_LIB = 0; // Whether to build the code as a shared library, which // must be loaded dynamically using dlopen(). // 0 here means this is not a shared lib: It is a main file. diff --git a/tests/runner.py b/tests/runner.py index 8d1f0674..a55b5ff5 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -7883,6 +7883,189 @@ elif 'browser' in str(sys.argv): finally: os.chdir(cwd) + def test_split(self): + # test HTML generation. + self.reftest(path_from_root('tests', 'htmltest.png')) + output = Popen(['python', EMCC, path_from_root('tests', 'hello_world_sdl.cpp'), '-o', 'something.js', '--split', '100']).communicate() + assert os.path.exists(os.path.join(self.get_dir(), 'something.js')), 'must be side js' + assert os.path.exists(os.path.join(self.get_dir(), 'something_functions.js')), 'must be side js' + assert os.path.exists(os.path.join(self.get_dir(), 'something.include.html')), 'must be side js' + + open(os.path.join(self.get_dir(), 'something.html'), 'w').write(''' + + <!doctype html> + <html lang="en-us"> + <head> + <meta charset="utf-8"> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <title>Emscripten-Generated Code</title> + <style> + .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } + canvas.emscripten { border: 1px solid black; } + textarea.emscripten { font-family: monospace; width: 80%; } + div.emscripten { text-align: center; } + </style> + </head> + <body> + <hr/> + <div class="emscripten" id="status">Downloading...</div> + <div class="emscripten"> + <progress value="0" max="100" id="progress" hidden=1></progress> + </div> + <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas> + <hr/> + <div class="emscripten"><input type="button" value="fullscreen" onclick="Module.requestFullScreen()"></div> + <hr/> + <textarea class="emscripten" id="output" rows="8"></textarea> + <hr> + <script type='text/javascript'> + // connect to canvas + var Module = { + preRun: [], + postRun: [], + print: (function() { + var element = document.getElementById('output'); + element.value = ''; // clear browser cache + return function(text) { + // These replacements are necessary if you render to raw HTML + //text = text.replace(/&/g, "&"); + //text = text.replace(/</g, "<"); + //text = text.replace(/>/g, ">"); + //text = text.replace('\\n', '<br>', 'g'); + element.value += text + "\\n"; + element.scrollTop = 99999; // focus on bottom + }; + })(), + printErr: function(text) { + if (0) { // XXX disabled for safety typeof dump == 'function') { + dump(text + '\\n'); // fast, straight to the real console + } else { + console.log(text); + } + }, + canvas: document.getElementById('canvas'), + setStatus: function(text) { + if (Module.setStatus.interval) clearInterval(Module.setStatus.interval); + var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/); + var statusElement = document.getElementById('status'); + var progressElement = document.getElementById('progress'); + if (m) { + text = m[1]; + progressElement.value = parseInt(m[2])*100; + progressElement.max = parseInt(m[4])*100; + progressElement.hidden = false; + } else { + progressElement.value = null; + progressElement.max = null; + progressElement.hidden = true; + } + statusElement.innerHTML = text; + }, + totalDependencies: 0, + monitorRunDependencies: function(left) { + this.totalDependencies = Math.max(this.totalDependencies, left); + Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.'); + } + }; + Module.setStatus('Downloading...'); + </script>''' + open(os.path.join(self.get_dir(), 'something.include.html')).read() + ''' + </body> + </html> + ''') + + self.run_browser('something.html', 'You should see "hello, world!" and a colored cube.', '/report_result?0') + + def test_split_in_source_filenames(self): + self.reftest(path_from_root('tests', 'htmltest.png')) + output = Popen(['python', EMCC, path_from_root('tests', 'hello_world_sdl.cpp'), '-o', 'something.js', '-g', '--split', '100']).communicate() + assert os.path.exists(os.path.join(self.get_dir(), 'something.js')), 'must be side js' + assert os.path.exists(self.get_dir() + '/something/' + path_from_root('tests', 'hello_world_sdl.cpp.js')), 'must be side js' + assert os.path.exists(os.path.join(self.get_dir(), 'something.include.html')), 'must be side js' + + open(os.path.join(self.get_dir(), 'something.html'), 'w').write(''' + + <!doctype html> + <html lang="en-us"> + <head> + <meta charset="utf-8"> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <title>Emscripten-Generated Code</title> + <style> + .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } + canvas.emscripten { border: 1px solid black; } + textarea.emscripten { font-family: monospace; width: 80%; } + div.emscripten { text-align: center; } + </style> + </head> + <body> + <hr/> + <div class="emscripten" id="status">Downloading...</div> + <div class="emscripten"> + <progress value="0" max="100" id="progress" hidden=1></progress> + </div> + <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas> + <hr/> + <div class="emscripten"><input type="button" value="fullscreen" onclick="Module.requestFullScreen()"></div> + <hr/> + <textarea class="emscripten" id="output" rows="8"></textarea> + <hr> + <script type='text/javascript'> + // connect to canvas + var Module = { + preRun: [], + postRun: [], + print: (function() { + var element = document.getElementById('output'); + element.value = ''; // clear browser cache + return function(text) { + // These replacements are necessary if you render to raw HTML + //text = text.replace(/&/g, "&"); + //text = text.replace(/</g, "<"); + //text = text.replace(/>/g, ">"); + //text = text.replace('\\n', '<br>', 'g'); + element.value += text + "\\n"; + element.scrollTop = 99999; // focus on bottom + }; + })(), + printErr: function(text) { + if (0) { // XXX disabled for safety typeof dump == 'function') { + dump(text + '\\n'); // fast, straight to the real console + } else { + console.log(text); + } + }, + canvas: document.getElementById('canvas'), + setStatus: function(text) { + if (Module.setStatus.interval) clearInterval(Module.setStatus.interval); + var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/); + var statusElement = document.getElementById('status'); + var progressElement = document.getElementById('progress'); + if (m) { + text = m[1]; + progressElement.value = parseInt(m[2])*100; + progressElement.max = parseInt(m[4])*100; + progressElement.hidden = false; + } else { + progressElement.value = null; + progressElement.max = null; + progressElement.hidden = true; + } + statusElement.innerHTML = text; + }, + totalDependencies: 0, + monitorRunDependencies: function(left) { + this.totalDependencies = Math.max(this.totalDependencies, left); + Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.'); + } + }; + Module.setStatus('Downloading...'); + </script>''' + open(os.path.join(self.get_dir(), 'something.include.html')).read() + ''' + </body> + </html> + ''') + + self.run_browser('something.html', 'You should see "hello, world!" and a colored cube.', '/report_result?0') + def test_compression(self): open(os.path.join(self.get_dir(), 'main.cpp'), 'w').write(self.with_report_result(r''' #include <stdio.h> diff --git a/tools/split.py b/tools/split.py new file mode 100755 index 00000000..ab1e97ca --- /dev/null +++ b/tools/split.py @@ -0,0 +1,94 @@ +import sys +import os + +def split_javascript_file(input_filename, output_filename_prefix, max_part_size_in_bytes): + + try: + # Contains the entire Emscripten generated Javascript code + input_file = open(input_filename,'r') + + # Javascript main file. On execution, this file needs to be loaded at last (!) + output_main_filename = output_filename_prefix + ".js" + output_main_file = open(output_main_filename,'w') + + # File with HTML script tags to load the Javascript files in HTML later on + output_html_include_file = open(output_filename_prefix + ".include.html",'w') + + # Variable will contain the source of a Javascript function if we find one during parsing + js_function = None + + # Dictionary with source file as key and an array of functions associated to that source file as value + function_buckets = {}; + + output_part_file = None + + # Iterate over Javascript source; write main file; parse function declarations. + for line in input_file: + if line == "//FUNCTION_BEGIN_MARKER\n": + js_function = "//Func\n" + elif line.startswith("//FUNCTION_END_MARKER_OF_SOURCE_FILE_"): + # At the end of the function marker we get the source file that is associated to that function. + associated_source_file_base = line[len("//FUNCTION_END_MARKER_OF_SOURCE_FILE_"):len(line)-1] + + if associated_source_file_base == "NO_SOURCE": + # Functions without associated source file are stored in a file in the base directory + associated_source_file_base = output_filename_prefix + "_functions"; + else: + # Functions with a known associated source file are stored in a file in the directory `output_filename_prefix` + associated_source_file_base = output_filename_prefix + os.path.realpath(associated_source_file_base) + + # Add the function to its respective file + if associated_source_file_base not in function_buckets: + function_buckets[associated_source_file_base] = [] + function_buckets[associated_source_file_base] += [js_function] + + # Clear the function read cache + js_function = None + else: + if js_function is None: + output_main_file.write(line) + else: + js_function += line + + # Iterate over all function buckets and write their functions to the associated files + # An associated file is split into chunks of `max_part_size_in_bytes` + for associated_source_file_base in function_buckets: + # At first we try to name the Javascript source file to match the assoicated source file + `.js` + js_source_file = associated_source_file_base + ".js" + + # Check if the directory of the Javascript source file exists + js_source_dir = os.path.dirname(js_source_file) + if len(js_source_dir) > 0 and not os.path.exists(js_source_dir): + os.makedirs(js_source_dir) + + output_part_file_counter = 0 + output_part_file = None + for js_function in function_buckets[associated_source_file_base]: + if output_part_file is None: + output_html_include_file.write("<script type=\"text/javascript\" src=\"" + js_source_file + "\"></script>") + output_part_file = open(js_source_file,'w') + + output_part_file.write(js_function) + + if output_part_file is not None and output_part_file.tell() > max_part_size_in_bytes: + output_part_file.close() + output_part_file = None + output_part_file_counter += 1 + js_source_file = associated_source_file_base + ".part" + str(output_part_file_counter) + ".js" + + if output_part_file is not None: + output_part_file.close() + output_part_file = None + + # Write the main Javascript file at last to the HTML includes because this file contains the code to start + # the execution of the generated Emscripten application and requires all the extracted functions. + output_html_include_file.write("<script type=\"text/javascript\" src=\"" + output_main_filename + "\"></script>") + + except Exception, e: + print >> sys.stderr, 'error: Splitting of Emscripten generated Javascript failed: %s' % str(e) + + finally: + if input_file is not None: input_file.close() + if output_main_file is not None: output_main_file.close() + if output_part_file is not None: output_part_file.close() + if output_html_include_file is not None: output_html_include_file.close()
\ No newline at end of file |