diff options
47 files changed, 3847 insertions, 3326 deletions
@@ -1,4 +1,4 @@ -The following authors have all licensed their contributions to Emscripten +The following authors have all licensed their contributions to Emscripten under the licensing terms detailed in LICENSE. (Authors keep copyright of their contributions, of course; they just grant @@ -96,4 +96,7 @@ a license to everyone to use it as detailed in LICENSE.) * Aidan Hobson Sayers <aidanhs@cantab.net> * Charlie Birks <admin@daftgames.net> * Ranger Harke <ranger.harke@autodesk.com> (copyright owned by Autodesk, Inc.) +* Tobias Vrinssen <tobias@vrinssen.de> +* Patrick R. Martin <patrick.martin.r@gmail.com> +* Richard Quirk <richard.quirk@gmail.com> diff --git a/cmake/Platform/Emscripten.cmake b/cmake/Platform/Emscripten.cmake index ec3f5383..73f2c8ad 100644 --- a/cmake/Platform/Emscripten.cmake +++ b/cmake/Platform/Emscripten.cmake @@ -139,3 +139,60 @@ set(CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO "-O2" CACHE STRING "Emscripten-over function(em_validate_asmjs_after_build target) add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E echo Validating build output for asm.js... COMMAND "python" ARGS "${EMSCRIPTEN_ROOT_PATH}/tools/validate_asmjs.py" "$<TARGET_FILE:${target}>") endfunction() + +# A global counter to guarantee unique names for js library files. +set(link_js_counter 1) + +# Internal function: Do not call from user CMakeLists.txt files. Use one of em_link_js_library()/em_link_pre_js()/em_link_post_js() instead. +function(em_add_tracked_link_flag target flagname) + get_target_property(props ${target} LINK_FLAGS) + # User can input list of JS files either as a single list, or as variable arguments to this function, so iterate over varargs, and treat each + # item in varargs as a list itself, to support both syntax forms. + foreach(jsFileList ${ARGN}) + foreach(jsfile ${jsFileList}) + # Add link command to the given JS file. + set(props "${props} ${flagname} \"${jsfile}\"") + + # If the user edits the JS file, we want to relink the emscripten application, but unfortunately it is not possible to make a link step + # depend directly on a source file. Instead, we must make a dummy no-op build target on that source file, and make the project depend on + # that target. + + # Sanitate the source .js filename to a good symbol name to use as a dummy filename. + get_filename_component(jsname "${jsfile}" NAME) + string(REGEX REPLACE "[/:\\\\.\ ]" "_" dummy_js_target ${jsname}) + set(dummy_lib_name ${target}_${link_js_counter}_${dummy_js_target}) + set(dummy_c_name "${CMAKE_BINARY_DIR}/${dummy_js_target}_tracker.c") + + # Create a new static library target that with a single dummy .c file. + add_library(${dummy_lib_name} STATIC ${dummy_c_name}) + # Make the dummy .c file depend on the .js file we are linking, so that if the .js file is edited, the dummy .c file, and hence the static library will be rebuild (no-op). This causes the main application to be relinked, which is what we want. + # This approach was recommended by http://www.cmake.org/pipermail/cmake/2010-May/037206.html + add_custom_command(OUTPUT ${dummy_c_name} COMMAND ${CMAKE_COMMAND} -E touch ${dummy_c_name} DEPENDS ${jsfile}) + target_link_libraries(${target} ${dummy_lib_name}) + + math(EXPR link_js_counter "${link_js_counter} + 1") + endforeach() + endforeach() + set_target_properties(${target} PROPERTIES LINK_FLAGS "${props}") +endfunction() + +# This function links a (list of ) .js library file(s) to the given CMake project. +# Example: em_link_js_library(my_executable "lib1.js" "lib2.js") +# will result in emcc passing --js-library lib1.js --js-library lib2.js to the emscripten linker, as well as +# tracking the modification timestamp between the linked .js files and the main project, so that editing the .js file +# will cause the target project to be relinked. +function(em_link_js_library target) + em_add_tracked_link_flag(${target} "--js-library" ${ARGN}) +endfunction() + +# This function is identical to em_link_js_library(), except the .js files will be added with '--pre-js file.js' command line flag, +# which is generally used to add some preamble .js code to a generated output file. +function(em_link_pre_js target) + em_add_tracked_link_flag(${target} "--pre-js" ${ARGN}) +endfunction() + +# This function is identical to em_link_js_library(), except the .js files will be added with '--post-js file.js' command line flag, +# which is generally used to add some postamble .js code to a generated output file. +function(em_link_post_js target) + em_add_tracked_link_flag(${target} "--post-js" ${ARGN}) +endfunction() @@ -238,6 +238,9 @@ Options that are modified or new in %s include: (see --llvm-opts), setting this has no effect. + Note that LLVM LTO is not perfectly stable yet, + and can can cause code to behave incorrectly. + --closure <on> 0: No closure compiler (default in -O2 and below) 1: Run closure compiler. This greatly reduces code size and may in some cases increase diff --git a/emscripten.py b/emscripten.py index 4d744fdd..19e2160d 100755 --- a/emscripten.py +++ b/emscripten.py @@ -39,7 +39,7 @@ def scan(ll, settings): if len(blockaddrs) > 0: settings['NECESSARY_BLOCKADDRS'] = blockaddrs -NUM_CHUNKS_PER_CORE = 1.25 +NUM_CHUNKS_PER_CORE = 1.0 MIN_CHUNK_SIZE = 1024*1024 MAX_CHUNK_SIZE = float(os.environ.get('EMSCRIPT_MAX_CHUNK_SIZE') or 'inf') # configuring this is just for debugging purposes @@ -209,7 +209,7 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, if cores > 1: intended_num_chunks = int(round(cores * NUM_CHUNKS_PER_CORE)) chunk_size = max(MIN_CHUNK_SIZE, total_ll_size / intended_num_chunks) - chunk_size += 3*len(meta) + len(forwarded_data)/3 # keep ratio of lots of function code to meta (expensive to process, and done in each parallel task) and forwarded data (less expensive but potentially significant) + chunk_size += 3*len(meta) # keep ratio of lots of function code to meta (expensive to process, and done in each parallel task) chunk_size = min(MAX_CHUNK_SIZE, chunk_size) else: chunk_size = MAX_CHUNK_SIZE # if 1 core, just use the max chunk size diff --git a/src/analyzer.js b/src/analyzer.js index b20dedff..750f2a4c 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -27,82 +27,69 @@ var SHADOW_FLIP = { i64: 'double', double: 'i64' }; //, i32: 'float', float: 'i3 function analyzer(data, sidePass) { var mainPass = !sidePass; - // Substrate - var substrate = new Substrate('Analyzer'); - - // Sorter - substrate.addActor('Sorter', { - processItem: function(item) { - item.items.sort(function (a, b) { return a.lineNum - b.lineNum }); - this.forwardItem(item, 'Gatherer'); + var item = { items: data }; + var data = item; + + // Gather + // Single-liners + ['globalVariable', 'functionStub', 'unparsedFunction', 'unparsedGlobals', 'unparsedTypes', 'alias'].forEach(function(intertype) { + var temp = splitter(item.items, function(item) { return item.intertype == intertype }); + item.items = temp.leftIn; + item[intertype + 's'] = temp.splitOut; + }); + var temp = splitter(item.items, function(item) { return item.intertype == 'type' }); + item.items = temp.leftIn; + temp.splitOut.forEach(function(type) { + //dprint('types', 'adding defined type: ' + type.name_); + Types.types[type.name_] = type; + if (QUANTUM_SIZE === 1) { + Types.fatTypes[type.name_] = copy(type); } }); - // Gatherer - substrate.addActor('Gatherer', { - processItem: function(item) { - // Single-liners - ['globalVariable', 'functionStub', 'unparsedFunction', 'unparsedGlobals', 'unparsedTypes', 'alias'].forEach(function(intertype) { - var temp = splitter(item.items, function(item) { return item.intertype == intertype }); - item.items = temp.leftIn; - item[intertype + 's'] = temp.splitOut; - }); - var temp = splitter(item.items, function(item) { return item.intertype == 'type' }); - item.items = temp.leftIn; - temp.splitOut.forEach(function(type) { - //dprint('types', 'adding defined type: ' + type.name_); - Types.types[type.name_] = type; - if (QUANTUM_SIZE === 1) { - Types.fatTypes[type.name_] = copy(type); - } - }); - - // Functions & labels - item.functions = []; - var currLabelFinished; // Sometimes LLVM puts a branch in the middle of a label. We need to ignore all lines after that. - item.items.sort(function(a, b) { return a.lineNum - b.lineNum }); - for (var i = 0; i < item.items.length; i++) { - var subItem = item.items[i]; - assert(subItem.lineNum); - if (subItem.intertype == 'function') { - item.functions.push(subItem); - subItem.endLineNum = null; - subItem.lines = []; // We will fill in the function lines after the legalizer, since it can modify them - subItem.labels = []; - subItem.forceEmulated = false; - - // no explicit 'entry' label in clang on LLVM 2.8 - most of the time, but not all the time! - so we add one if necessary - if (item.items[i+1].intertype !== 'label') { - item.items.splice(i+1, 0, { - intertype: 'label', - ident: ENTRY_IDENT, - lineNum: subItem.lineNum + '.5' - }); - } - } else if (subItem.intertype == 'functionEnd') { - item.functions.slice(-1)[0].endLineNum = subItem.lineNum; - } else if (subItem.intertype == 'label') { - item.functions.slice(-1)[0].labels.push(subItem); - subItem.lines = []; - currLabelFinished = false; - } else if (item.functions.length > 0 && item.functions.slice(-1)[0].endLineNum === null) { - // Internal line - if (!currLabelFinished) { - item.functions.slice(-1)[0].labels.slice(-1)[0].lines.push(subItem); // If this line fails, perhaps missing a label? - if (subItem.intertype in LABEL_ENDERS) { - currLabelFinished = true; - } - } else { - print('// WARNING: content after a branch in a label, line: ' + subItem.lineNum); - } - } else { - throw 'ERROR: what is this? ' + dump(subItem); + // Functions & labels + item.functions = []; + var currLabelFinished = false; // Sometimes LLVM puts a branch in the middle of a label. We need to ignore all lines after that. + item.items.sort(function(a, b) { return a.lineNum - b.lineNum }); + for (var i = 0; i < item.items.length; i++) { + var subItem = item.items[i]; + assert(subItem.lineNum); + if (subItem.intertype == 'function') { + item.functions.push(subItem); + subItem.endLineNum = null; + subItem.lines = []; // We will fill in the function lines after the legalizer, since it can modify them + subItem.labels = []; + subItem.forceEmulated = false; + + // no explicit 'entry' label in clang on LLVM 2.8 - most of the time, but not all the time! - so we add one if necessary + if (item.items[i+1].intertype !== 'label') { + item.items.splice(i+1, 0, { + intertype: 'label', + ident: ENTRY_IDENT, + lineNum: subItem.lineNum + '.5' + }); + } + } else if (subItem.intertype == 'functionEnd') { + item.functions.slice(-1)[0].endLineNum = subItem.lineNum; + } else if (subItem.intertype == 'label') { + item.functions.slice(-1)[0].labels.push(subItem); + subItem.lines = []; + currLabelFinished = false; + } else if (item.functions.length > 0 && item.functions.slice(-1)[0].endLineNum === null) { + // Internal line + if (!currLabelFinished) { + item.functions.slice(-1)[0].labels.slice(-1)[0].lines.push(subItem); // If this line fails, perhaps missing a label? + if (subItem.intertype in LABEL_ENDERS) { + currLabelFinished = true; } + } else { + print('// WARNING: content after a branch in a label, line: ' + subItem.lineNum); } - delete item.items; - this.forwardItem(item, 'CastAway'); + } else { + throw 'ERROR: what is this? ' + dump(subItem); } - }); + } + delete item.items; // CastAway - try to remove bitcasts of double<-->i64, which LLVM sometimes generates unnecessarily // (load a double, convert to i64, use as i64). @@ -113,75 +100,72 @@ function analyzer(data, sidePass) { // Note that aside from being an optimization, this is needed for correctness in some cases: If code // assumes it can bitcast a double to an i64 and back and forth without loss, that may be violated // due to NaN canonicalization. - substrate.addActor('CastAway', { - processItem: function(item) { - this.forwardItem(item, 'Legalizer'); - if (USE_TYPED_ARRAYS != 2) return; + function castAway() { + if (USE_TYPED_ARRAYS != 2) return; - item.functions.forEach(function(func) { - var has = false; - func.labels.forEach(function(label) { - var lines = label.lines; - for (var i = 0; i < lines.length; i++) { - var line = lines[i]; - if (line.intertype == 'bitcast' && line.type in SHADOW_FLIP) { - has = true; - } + item.functions.forEach(function(func) { + var has = false; + func.labels.forEach(function(label) { + var lines = label.lines; + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + if (line.intertype == 'bitcast' && line.type in SHADOW_FLIP) { + has = true; } - }); - if (!has) return; - // there are integer<->floating-point bitcasts, create shadows for everything - var shadowed = {}; - func.labels.forEach(function(label) { - var lines = label.lines; - var i = 0; - while (i < lines.length) { - var lines = label.lines; - var line = lines[i]; - if (line.intertype == 'load' && line.type in SHADOW_FLIP) { - if (line.pointer.intertype != 'value') { i++; continue } // TODO - shadowed[line.assignTo] = 1; - var shadow = line.assignTo + '$$SHADOW'; - var flip = SHADOW_FLIP[line.type]; - lines.splice(i + 1, 0, { // if necessary this element will be legalized in the next phase - tokens: null, - indent: 2, - lineNum: line.lineNum + 0.5, - assignTo: shadow, - intertype: 'load', - pointerType: flip + '*', - type: flip, - valueType: flip, - pointer: { - intertype: 'value', - ident: line.pointer.ident, - type: flip + '*' - }, - align: line.align, - ident: line.ident - }); - // note: no need to update func.lines, it is generated in a later pass - i++; - } + } + }); + if (!has) return; + // there are integer<->floating-point bitcasts, create shadows for everything + var shadowed = {}; + func.labels.forEach(function(label) { + var lines = label.lines; + var i = 0; + while (i < lines.length) { + var lines = label.lines; + var line = lines[i]; + if (line.intertype == 'load' && line.type in SHADOW_FLIP) { + if (line.pointer.intertype != 'value') { i++; continue } // TODO + shadowed[line.assignTo] = 1; + var shadow = line.assignTo + '$$SHADOW'; + var flip = SHADOW_FLIP[line.type]; + lines.splice(i + 1, 0, { // if necessary this element will be legalized in the next phase + tokens: null, + indent: 2, + lineNum: line.lineNum + 0.5, + assignTo: shadow, + intertype: 'load', + pointerType: flip + '*', + type: flip, + valueType: flip, + pointer: { + intertype: 'value', + ident: line.pointer.ident, + type: flip + '*' + }, + align: line.align, + ident: line.ident + }); + // note: no need to update func.lines, it is generated in a later pass i++; } - }); - // use shadows where possible - func.labels.forEach(function(label) { - var lines = label.lines; - for (var i = 0; i < lines.length; i++) { - var line = lines[i]; - if (line.intertype == 'bitcast' && line.type in SHADOW_FLIP && line.ident in shadowed) { - var shadow = line.ident + '$$SHADOW'; - line.params[0].ident = shadow; - line.params[0].type = line.type; - line.type2 = line.type; - } + i++; + } + }); + // use shadows where possible + func.labels.forEach(function(label) { + var lines = label.lines; + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + if (line.intertype == 'bitcast' && line.type in SHADOW_FLIP && line.ident in shadowed) { + var shadow = line.ident + '$$SHADOW'; + line.params[0].ident = shadow; + line.params[0].type = line.type; + line.type2 = line.type; } - }); + } }); - } - }); + }); + } // Legalize LLVM unrealistic types into realistic types. // @@ -196,725 +180,722 @@ function analyzer(data, sidePass) { // Currently we just legalize completely unrealistic types into bundles of i32s, and just // the most common instructions that can be involved with such types: load, store, shifts, // trunc and zext. - substrate.addActor('Legalizer', { - processItem: function(data) { - // Legalization - if (USE_TYPED_ARRAYS == 2) { - function getLegalVars(base, bits, allowLegal) { - bits = bits || 32; // things like pointers are all i32, but show up as 0 bits from getBits - if (allowLegal && bits <= 32) return [{ ident: base + ('i' + bits in Runtime.INT_TYPES ? '' : '$0'), bits: bits }]; - if (isNumber(base)) return getLegalLiterals(base, bits); - if (base[0] == '{') { - warnOnce('seeing source of illegal data ' + base + ', likely an inline struct - assuming zeroinit'); - return getLegalLiterals('0', bits); - } - var ret = new Array(Math.ceil(bits/32)); - var i = 0; - if (base == 'zeroinitializer' || base == 'undef') base = 0; - while (bits > 0) { - ret[i] = { ident: base ? base + '$' + i : '0', bits: Math.min(32, bits) }; - bits -= 32; - i++; - } - return ret; + function legalizer() { + // Legalization + if (USE_TYPED_ARRAYS == 2) { + function getLegalVars(base, bits, allowLegal) { + bits = bits || 32; // things like pointers are all i32, but show up as 0 bits from getBits + if (allowLegal && bits <= 32) return [{ ident: base + ('i' + bits in Runtime.INT_TYPES ? '' : '$0'), bits: bits }]; + if (isNumber(base)) return getLegalLiterals(base, bits); + if (base[0] == '{') { + warnOnce('seeing source of illegal data ' + base + ', likely an inline struct - assuming zeroinit'); + return getLegalLiterals('0', bits); } - function getLegalLiterals(text, bits) { - var parsed = parseArbitraryInt(text, bits); - var ret = new Array(Math.ceil(bits/32)); - var i = 0; - while (bits > 0) { - ret[i] = { ident: (parsed[i]|0).toString(), bits: Math.min(32, bits) }; // resign all values - bits -= 32; - i++; - } - return ret; - } - function getLegalStructuralParts(value) { - return value.params.slice(0); + var ret = new Array(Math.ceil(bits/32)); + var i = 0; + if (base == 'zeroinitializer' || base == 'undef') base = 0; + while (bits > 0) { + ret[i] = { ident: base ? base + '$' + i : '0', bits: Math.min(32, bits) }; + bits -= 32; + i++; } - function getLegalParams(params, bits) { - return params.map(function(param) { - var value = param.value || param; - if (isNumber(value.ident)) { - return getLegalLiterals(value.ident, bits); - } else if (value.intertype == 'structvalue') { - return getLegalStructuralParts(value).map(function(part) { - return { ident: part.ident, bits: part.type.substr(1) }; - }); - } else { - return getLegalVars(value.ident, bits); - } - }); + return ret; + } + function getLegalLiterals(text, bits) { + var parsed = parseArbitraryInt(text, bits); + var ret = new Array(Math.ceil(bits/32)); + var i = 0; + while (bits > 0) { + ret[i] = { ident: (parsed[i]|0).toString(), bits: Math.min(32, bits) }; // resign all values + bits -= 32; + i++; } - // Uses the right factor to multiply line numbers by so that they fit in between - // the line[i] and the line after it - function interpLines(lines, i, toAdd) { - var prev = i >= 0 ? lines[i].lineNum : -1; - var next = (i < lines.length-1) ? lines[i+1].lineNum : (lines[i].lineNum + 0.5); - var factor = (next - prev)/(4*toAdd.length+3); - for (var k = 0; k < toAdd.length; k++) { - toAdd[k].lineNum = prev + ((k+1)*factor); - assert(k == 0 || toAdd[k].lineNum > toAdd[k-1].lineNum); + return ret; + } + function getLegalStructuralParts(value) { + return value.params.slice(0); + } + function getLegalParams(params, bits) { + return params.map(function(param) { + var value = param.value || param; + if (isNumber(value.ident)) { + return getLegalLiterals(value.ident, bits); + } else if (value.intertype == 'structvalue') { + return getLegalStructuralParts(value).map(function(part) { + return { ident: part.ident, bits: part.type.substr(1) }; + }); + } else { + return getLegalVars(value.ident, bits); } + }); + } + // Uses the right factor to multiply line numbers by so that they fit in between + // the line[i] and the line after it + function interpLines(lines, i, toAdd) { + var prev = i >= 0 ? lines[i].lineNum : -1; + var next = (i < lines.length-1) ? lines[i+1].lineNum : (lines[i].lineNum + 0.5); + var factor = (next - prev)/(4*toAdd.length+3); + for (var k = 0; k < toAdd.length; k++) { + toAdd[k].lineNum = prev + ((k+1)*factor); + assert(k == 0 || toAdd[k].lineNum > toAdd[k-1].lineNum); } - function removeAndAdd(lines, i, toAdd) { - var item = lines[i]; - interpLines(lines, i, toAdd); - Array.prototype.splice.apply(lines, [i, 1].concat(toAdd)); - if (i > 0) assert(lines[i].lineNum > lines[i-1].lineNum); - if (i + toAdd.length < lines.length) assert(lines[i + toAdd.length - 1].lineNum < lines[i + toAdd.length].lineNum); - return toAdd.length; - } - function legalizeFunctionParameters(params) { - var i = 0; - while (i < params.length) { - var param = params[i]; - if (param.intertype == 'value' && isIllegalType(param.type)) { - var toAdd = getLegalVars(param.ident, getBits(param.type)).map(function(element) { - return { - intertype: 'value', - type: 'i' + element.bits, - ident: element.ident, - byval: 0 - }; - }); - Array.prototype.splice.apply(params, [i, 1].concat(toAdd)); - i += toAdd.length; - continue; - } else if (param.intertype == 'structvalue') { - // 'flatten' out the struct into scalars - var toAdd = param.params; - toAdd.forEach(function(param) { - param.byval = 0; - }); - Array.prototype.splice.apply(params, [i, 1].concat(toAdd)); - continue; // do not increment i; proceed to process the new params - } - i++; + } + function removeAndAdd(lines, i, toAdd) { + var item = lines[i]; + interpLines(lines, i, toAdd); |