diff options
35 files changed, 1078 insertions, 215 deletions
@@ -99,5 +99,7 @@ a license to everyone to use it as detailed in LICENSE.) * Tobias Vrinssen <tobias@vrinssen.de> * Patrick R. Martin <patrick.martin.r@gmail.com> * Richard Quirk <richard.quirk@gmail.com> +* Marcos Scriven <marcos@scriven.org> +* Antoine Lambert <antoine.lambert33@gmail.com> * Daniel Aquino <mr.danielaquino@gmail.com> @@ -129,18 +129,18 @@ Most normal gcc/g++ options will work, for example: Options that are modified or new in %s include: -O0 No optimizations (default) -O1 Simple optimizations, including asm.js, LLVM -O1 - optimizations, and no runtime assertions + optimizations, relooping, and no runtime assertions or C++ exception catching (to re-enable C++ exception catching, use - -s DISABLE_EXCEPTION_CATCHING=0 ). - (For details on the affects of different - opt levels, see apply_opt_level() in - tools/shared.py and also src/settings.js.) - -O2 As -O1, plus the relooper (loop recreation), - LLVM -O3 optimizations, and + -s DISABLE_EXCEPTION_CATCHING=0 ), and enables -s ALIASING_FUNCTION_POINTERS=1 + (For details on the affects of different + opt levels, see apply_opt_level() in + tools/shared.py and also src/settings.js.) + -O2 As -O1, plus various js-level optimizations and + LLVM -O3 optimizations -O3 As -O2, plus dangerous optimizations that may break the generated code! This adds @@ -258,6 +258,9 @@ Options that are modified or new in %s include: try adjusting JAVA_HEAP_SIZE in the environment (for example, to 4096m for 4GB). + Note: Closure is only run if js opts are being + done (-O2 or above, or --js-opts 1). + --js-transform <cmd> <cmd> will be called on the generated code before it is optimized. This lets you modify the JavaScript, for example adding some code @@ -933,7 +936,7 @@ try: if default_cxx_std: newargs = newargs + [default_cxx_std] - if js_opts is None: js_opts = opt_level >= 1 + if js_opts is None: js_opts = opt_level >= 2 if llvm_opts is None: llvm_opts = LLVM_OPT_LEVEL[opt_level] if llvm_lto is None and opt_level >= 3: llvm_lto = 3 if opt_level == 0: debug_level = 4 @@ -1105,6 +1108,16 @@ try: shared.Settings.CORRECT_OVERFLOWS = 1 assert not shared.Settings.PGO, 'cannot run PGO in ASM_JS mode' + heap = 4096 + while heap < shared.Settings.TOTAL_MEMORY: + if heap <= 16*1024*1024: + heap *= 2 + else: + heap += 16*1024*1024 + if heap != shared.Settings.TOTAL_MEMORY: + logging.warning('increasing TOTAL_MEMORY to %d to be more reasonable for asm.js' % heap) + shared.Settings.TOTAL_MEMORY = heap + if shared.Settings.CORRECT_SIGNS >= 2 or shared.Settings.CORRECT_OVERFLOWS >= 2 or shared.Settings.CORRECT_ROUNDINGS >= 2: debug_level = 4 # must keep debug info to do line-by-line operations @@ -1218,7 +1231,7 @@ try: file_suffix = filename_type_suffix(input_file) if file_suffix.endswith(SOURCE_SUFFIXES): temp_file = temp_files[i] - logging.debug('optimizing %s with -O%d' % (input_file, llvm_opts)) + logging.debug('optimizing %s with -O%s' % (input_file, llvm_opts)) shared.Building.llvm_opt(temp_file, llvm_opts) # If we were just asked to generate bitcode, stop there diff --git a/emscripten.py b/emscripten.py index 2e90fa48..b7f85e6f 100755 --- a/emscripten.py +++ b/emscripten.py @@ -49,15 +49,8 @@ if STDERR_FILE: logging.info('logging stderr in js compiler phase into %s' % STDERR_FILE) STDERR_FILE = open(STDERR_FILE, 'w') -def process_funcs((i, funcs, meta, settings_file, compiler, forwarded_file, libraries, compiler_engine, temp_files, DEBUG)): +def process_funcs((i, funcs_file, meta, settings_file, compiler, forwarded_file, libraries, compiler_engine, DEBUG)): try: - funcs_file = temp_files.get('.func_%d.ll' % i).name - f = open(funcs_file, 'w') - f.write(funcs) - funcs = None - f.write('\n') - f.write(meta) - f.close() #print >> sys.stderr, 'running', str([settings_file, funcs_file, 'funcs', forwarded_file] + libraries).replace("'/", "'") # can use this in src/compiler_funcs.html arguments, # # just copy temp dir to under this one out = jsrun.run_js( @@ -100,42 +93,42 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, ll = open(infile).read() scan(ll, settings) total_ll_size = len(ll) - ll = None # allow collection if DEBUG: logging.debug(' emscript: scan took %s seconds' % (time.time() - t)) # Split input into the relevant parts for each phase + + if DEBUG: t = time.time() + pre = [] funcs = [] # split up functions here, for parallelism later - meta = [] # needed by each function XXX - if DEBUG: t = time.time() - in_func = False - ll_lines = open(infile).readlines() - curr_func = None - for line in ll_lines: - if in_func: - curr_func.append(line) - if line.startswith('}'): - in_func = False - funcs.append((curr_func[0], ''.join(curr_func))) # use the entire line as the identifier - # pre needs to know about all implemented functions, even for non-pre func - pre.append(curr_func[0]) - pre.append(line) - curr_func = None - else: - if line.startswith(';'): continue - if line.startswith('define '): - in_func = True - curr_func = [line] - elif line.find(' = type { ') > 0: - pre.append(line) # type - elif line.startswith('!'): - if line.startswith('!llvm.module'): continue # we can ignore that - meta.append(line) # metadata - else: - pre.append(line) # pre needs it so we know about globals in pre and funcs. So emit globals there - ll_lines = None - meta = ''.join(meta) + meta_start = ll.find('\n!') + if meta_start > 0: + meta = ll[meta_start:] + else: + meta = '' + meta_start = -1 + + start = ll.find('\n') if ll[0] == ';' else 0 # ignore first line, which contains ; ModuleID = '/dir name' + + func_start = start + last = func_start + while 1: + last = func_start + func_start = ll.find('\ndefine ', func_start) + if func_start > last: + pre.append(ll[last:min(func_start+1, meta_start)] + '\n') + if func_start < 0: + pre.append(ll[last:meta_start] + '\n') + break + header = ll[func_start+1:ll.find('\n', func_start+1)+1] + end = ll.find('\n}', func_start) + last = end+3 + funcs.append((header, ll[func_start+1:last])) + pre.append(header + '}\n') + func_start = last + ll = None + if DEBUG and len(meta) > 1024*1024: logging.debug('emscript warning: large amounts of metadata, will slow things down') if DEBUG: logging.debug(' emscript: split took %s seconds' % (time.time() - t)) @@ -195,6 +188,7 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, jcache.set(shortkey, keys, out) pre, forwarded_data = out.split('//FORWARDED_DATA:') forwarded_file = temp_files.get('.json').name + pre_input = None open(forwarded_file, 'w').write(forwarded_data) if DEBUG: logging.debug(' emscript: phase 1 took %s seconds' % (time.time() - t)) @@ -254,11 +248,20 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, if DEBUG: logging.debug(' emscript: phase 2 working on %d chunks %s (intended chunk size: %.2f MB, meta: %.2f MB, forwarded: %.2f MB, total: %.2f MB)' % (len(chunks), ('using %d cores' % cores) if len(chunks) > 1 else '', chunk_size/(1024*1024.), len(meta)/(1024*1024.), len(forwarded_data)/(1024*1024.), total_ll_size/(1024*1024.))) - commands = [ - (i, chunk, meta, settings_file, compiler, forwarded_file, libraries, compiler_engine,# + ['--prof'], - temp_files, DEBUG) - for i, chunk in enumerate(chunks) - ] + commands = [] + for i in range(len(chunks)): + funcs_file = temp_files.get('.func_%d.ll' % i).name + f = open(funcs_file, 'w') + f.write(chunks[i]) + if not jcache: + chunks[i] = None # leave chunks array alive (need its length later) + f.write('\n') + f.write(meta) + f.close() + commands.append( + (i, funcs_file, meta, settings_file, compiler, forwarded_file, libraries, compiler_engine,# + ['--prof'], + DEBUG) + ) if len(chunks) > 1: pool = multiprocessing.Pool(processes=cores) @@ -346,7 +349,7 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, else: curr = i i += settings['FUNCTION_POINTER_ALIGNMENT'] - #logging.debug('function indexing', indexed, curr, sig) + #logging.debug('function indexing ' + str([indexed, curr, sig])) forwarded_json['Functions']['indexedFunctions'][indexed] = curr # make sure not to modify this python object later - we use it in indexize def split_32(x): diff --git a/src/analyzer.js b/src/analyzer.js index 17ad26ad..95fbccc7 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -188,7 +188,7 @@ function analyzer(data, sidePass) { 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 (allowLegal && bits <= 32) return [{ intertype: 'value', ident: base + ('i' + bits in Runtime.INT_TYPES ? '' : '$0'), bits: bits, type: 'i' + bits }]; if (isNumber(base)) return getLegalLiterals(base, bits); if (base[0] == '{') { warnOnce('seeing source of illegal data ' + base + ', likely an inline struct - assuming zeroinit'); @@ -198,7 +198,7 @@ function analyzer(data, sidePass) { 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) }; + ret[i] = { intertype: 'value', ident: base ? base + '$' + i : '0', bits: Math.min(32, bits), type: 'i' + Math.min(32, bits) }; bits -= 32; i++; } @@ -209,7 +209,7 @@ function analyzer(data, sidePass) { 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 + ret[i] = { intertype: 'value', ident: (parsed[i]|0).toString(), bits: Math.min(32, bits), type: 'i' + Math.min(32, bits) }; // resign all values bits -= 32; i++; } @@ -225,7 +225,8 @@ function analyzer(data, sidePass) { 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) }; + part.bits = part.type.substr(1); // can be some nested IR, like LLVM calls + return part; }); } else { return getLegalVars(value.ident, bits); @@ -550,11 +551,7 @@ function analyzer(data, sidePass) { return { intertype: 'phiparam', label: param.label, - value: { - intertype: 'value', - ident: values[k++][j].ident, - type: 'i' + element.bits, - } + value: values[k++][j] }; }) }); @@ -802,27 +799,65 @@ function analyzer(data, sidePass) { var whole = shifts >= 0 ? Math.floor(shifts/32) : Math.ceil(shifts/32); var fraction = Math.abs(shifts % 32); if (signed) { - var signedFill = '(' + makeSignOp(sourceElements[sourceElements.length-1].ident, 'i' + sourceElements[sourceElements.length-1].bits, 're', 1, 1) + ' < 0 ? -1 : 0)'; - var signedKeepAlive = { intertype: 'value', ident: sourceElements[sourceElements.length-1].ident, type: 'i32' }; - } - for (var j = 0; j < targetElements.length; j++) { - var result = { - intertype: 'value', - ident: (j + whole >= 0 && j + whole < sourceElements.length) ? sourceElements[j + whole].ident : (signed ? signedFill : '0'), - params: [(signed && j + whole > sourceElements.length) ? signedKeepAlive : null], + var signedFill = { + intertype: 'mathop', + op: 'select', + variant: 's', type: 'i32', + params: [{ + intertype: 'mathop', + op: 'icmp', + variant: 'slt', + type: 'i32', + params: [ + { intertype: 'value', ident: sourceElements[sourceElements.length-1].ident, type: 'i' + Math.min(sourceBits, 32) }, + { intertype: 'value', ident: '0', type: 'i32' } + ] + }, + { intertype: 'value', ident: '-1', type: 'i32' }, + { intertype: 'value', ident: '0', type: 'i32' }, + ] }; - if (j == 0 && sourceBits < 32) { - // zext sign correction - result.ident = makeSignOp(result.ident, 'i' + sourceBits, isUnsignedOp(value.op) ? 'un' : 're', 1, 1); - } - if (fraction != 0) { - var other = { + } + for (var j = 0; j < targetElements.length; j++) { + var inBounds = j + whole >= 0 && j + whole < sourceElements.length; + var result; + if (inBounds || !signed) { + result = { intertype: 'value', - ident: (j + sign + whole >= 0 && j + sign + whole < sourceElements.length) ? sourceElements[j + sign + whole].ident : (signed ? signedFill : '0'), - params: [(signed && j + sign + whole > sourceElements.length) ? signedKeepAlive : null], - type: 'i32', + ident: inBounds ? sourceElements[j + whole].ident : '0', + type: 'i' + Math.min(sourceBits, 32), }; + if (j == 0 && sourceBits < 32) { + // zext sign correction + var result2 = { + intertype: 'mathop', + op: isUnsignedOp(value.op) ? 'zext' : 'sext', + params: [result, { + intertype: 'type', + ident: 'i32', + type: 'i' + sourceBits + }], + type: 'i32' + }; + result = result2; + } + } else { + // out of bounds and signed + result = copy(signedFill); + } + if (fraction != 0) { + var other; + var otherInBounds = j + sign + whole >= 0 && j + sign + whole < sourceElements.length; + if (otherInBounds || !signed) { + other = { + intertype: 'value', + ident: otherInBounds ? sourceElements[j + sign + whole].ident : '0', + type: 'i32', + }; + } else { + other = copy(signedFill); + } other = { intertype: 'mathop', op: shiftOp, @@ -872,10 +907,17 @@ function analyzer(data, sidePass) { } if (targetBits <= 32) { // We are generating a normal legal type here - legalValue = { - intertype: 'value', - ident: targetElements[0].ident + (targetBits < 32 ? '&' + (Math.pow(2, targetBits)-1) : ''), - type: 'rawJS' + legalValue = { intertype: 'value', ident: targetElements[0].ident, type: 'i32' }; + if (targetBits < 32) { + legalValue = { + intertype: 'mathop', + op: 'and', + type: 'i32', + params: [ + legalValue, + { intertype: 'value', ident: (Math.pow(2, targetBits)-1).toString(), type: 'i32' } + ] + } }; legalValue.assignTo = item.assignTo; toAdd.push(legalValue); @@ -1117,7 +1159,7 @@ function analyzer(data, sidePass) { rawLinesIndex: i }; if (variable.origin === 'alloca') { - variable.allocatedNum = item.allocatedNum; + variable.allocatedNum = item.ident; } if (variable.origin === 'call') { variable.type = getReturnType(variable.type); @@ -1608,9 +1650,9 @@ function analyzer(data, sidePass) { var lines = func.labels[0].lines; for (var i = 0; i < lines.length; i++) { var item = lines[i]; - if (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.allocatedNum)) break; + if (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.ident)) break; item.allocatedSize = func.variables[item.assignTo].impl === VAR_EMULATED ? - calcAllocatedSize(item.allocatedType)*item.allocatedNum: 0; + calcAllocatedSize(item.allocatedType)*item.ident: 0; if (USE_TYPED_ARRAYS === 2) { // We need to keep the stack aligned item.allocatedSize = Runtime.forceAlign(item.allocatedSize, Runtime.STACK_ALIGN); @@ -1619,7 +1661,7 @@ function analyzer(data, sidePass) { var index = 0; for (var i = 0; i < lines.length; i++) { var item = lines[i]; - if (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.allocatedNum)) break; + if (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.ident)) break; item.allocatedIndex = index; index += item.allocatedSize; delete item.allocatedSize; @@ -1647,7 +1689,7 @@ function analyzer(data, sidePass) { for (var i = 0; i < lines.length; i++) { var item = lines[i]; - if (!finishedInitial && (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.allocatedNum))) { + if (!finishedInitial && (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.ident))) { finishedInitial = true; } if (item.intertype == 'alloca' && finishedInitial) { diff --git a/src/compiler.js b/src/compiler.js index 90060837..d490f454 100644 --- a/src/compiler.js +++ b/src/compiler.js @@ -185,7 +185,6 @@ if (SAFE_HEAP) USE_BSS = 0; // must initialize heap for safe heap assert(!(USE_TYPED_ARRAYS === 2 && QUANTUM_SIZE !== 4), 'For USE_TYPED_ARRAYS == 2, must have normal QUANTUM_SIZE of 4'); if (ASM_JS) { assert(!ALLOW_MEMORY_GROWTH, 'Cannot grow asm.js heap'); - assert((TOTAL_MEMORY&(TOTAL_MEMORY-1)) == 0, 'asm.js heap must be power of 2'); } assert(!(!NAMED_GLOBALS && BUILD_AS_SHARED_LIB), 'shared libraries must have named globals'); @@ -207,7 +206,12 @@ if (phase == 'pre') { if (VERBOSE) printErr('VERBOSE is on, this generates a lot of output and can slow down compilation'); // Load struct and define information. -var temp = JSON.parse(read(STRUCT_INFO)); +try { + var temp = JSON.parse(read(STRUCT_INFO)); +} catch(e) { + printErr('cannot load struct info at ' + STRUCT_INFO + ' : ' + e + ', trying in current dir'); + temp = JSON.parse(read('struct_info.compiled.json')); +} C_STRUCTS = temp.structs; C_DEFINES = temp.defines; @@ -312,3 +316,6 @@ if (ll_file) { } } +//var M = keys(tokenCacheMisses).map(function(m) { return [m, misses[m]] }).sort(function(a, b) { return a[1] - b[1] }); +//printErr(dump(M.slice(M.length-10))); + diff --git a/src/intertyper.js b/src/intertyper.js index 09bdaa33..781c8187 100644 --- a/src/intertyper.js +++ b/src/intertyper.js @@ -5,14 +5,16 @@ var fastPaths = 0, slowPaths = 0; +var tokenCache = {}; +['=', 'i32', 'label', ';', '4', '0', '1', '2', '255', 'align', 'i8*', 'i8', 'i16', 'getelementptr', 'inbounds', 'unnamed_addr', 'x', 'load', 'preds', 'br', 'i32*', 'i1', 'store', '<label>', 'constant', 'c', 'private', 'null', 'internal', 'to', 'bitcast', 'define', 'nounwind', 'nocapture', '%this', 'call', '...'].forEach(function(text) { tokenCache[text] = { text: text } }); + +//var tokenCacheMisses = {}; + // Line tokenizer -function tokenizer(item, inner) { - //assert(item.lineNum != 40000); - //if (item.lineNum) print(item.lineNum); +function tokenize(text, lineNum) { var tokens = []; var quotes = 0; var lastToken = null; - var CHUNKSIZE = 64; // How much forward to peek forward. Too much means too many string segments copied // Note: '{' is not an encloser, as its use in functions is split over many lines var enclosers = { '[': 0, @@ -26,22 +28,33 @@ function tokenizer(item, inner) { function makeToken(text) { if (text.length == 0) return; // merge certain tokens - if (lastToken && ( (lastToken.text == '%' && text[0] == '"') || /^\**$/.test(text) ) ) { + if (lastToken && /^\**$/.test(text)) { lastToken.text += text; return; } + var cached = tokenCache[text]; + if (cached) { + //assert(cached.text === text); + tokens.push(cached); + lastToken = cached; + return; + } + //tokenCacheMisses[text] = (misses[text] || 0) + 1; + var token = { text: text }; if (text[0] in enclosers) { - token.item = tokenizer({ - lineText: text.substr(1, text.length-2) - }, true); + token.item = tokenize(text.substr(1, text.length-2)); token.type = text[0]; } // merge certain tokens if (lastToken && isType(lastToken.text) && isFunctionDef(token)) { + if (lastToken.text in tokenCache) { + // create a copy of the cached value + lastToken = tokens[tokens.length-1] = { text: lastToken.text }; + } lastToken.text += ' ' + text; } else if (lastToken && text[0] == '}') { // }, }*, etc. var openBrace = tokens.length-1; @@ -63,7 +76,7 @@ function tokenizer(item, inner) { } } // Split using meaningful characters - var lineText = item.lineText + ' '; + var lineText = text + ' '; var re = /[\[\]\(\)<>, "]/g; var segments = lineText.split(re); segments.pop(); @@ -141,15 +154,11 @@ function tokenizer(item, inner) { var newItem = { tokens: tokens, indent: lineText.search(/[^ ]/), - lineNum: item.lineNum + lineNum: lineNum || 0 }; return newItem; } -function tokenize(text) { - return tokenizer({ lineText: text }, true); -} - // Handy sets var ENCLOSER_STARTERS = set('[', '(', '<'); @@ -251,7 +260,7 @@ function intertyper(lines, sidePass, baseLineNums) { if (mainPass && /^}.*/.test(line)) { inFunction = false; if (mainPass) { - var func = funcHeaderHandler(tokenizer({ lineText: currFunctionLines[0], lineNum: currFunctionLineNum }, true)); + var func = funcHeaderHandler(tokenize(currFunctionLines[0], currFunctionLineNum)); if (SKIP_STACK_IN_SMALL && /emscripten_autodebug/.exec(func.ident)) { warnOnce('Disabling SKIP_STACK_IN_SMALL because we are apparently processing autodebugger data'); @@ -766,15 +775,13 @@ function intertyper(lines, sidePass, baseLineNums) { return item; } // 'alloca' - var allocaPossibleVars = ['allocatedNum']; function allocaHandler(item) { item.intertype = 'alloca'; item.allocatedType = item.tokens[1].text; if (item.tokens.length > 3 && Runtime.isNumberType(item.tokens[3].text)) { - item.allocatedNum = toNiceIdent(item.tokens[4].text); - item.possibleVars = allocaPossibleVars; + item.ident = toNiceIdent(item.tokens[4].text); } else { - item.allocatedNum = 1; + item.ident = 1; } item.type = addPointing(item.tokens[1].text); // type of pointer we will get Types.needAnalysis[item.type] = 0; @@ -1099,7 +1106,7 @@ function intertyper(lines, sidePass, baseLineNums) { if (ret) { if (COMPILER_ASSERTIONS) { //printErr(['\n', dump(ret), '\n', dump(triager(tokenizer(line)))]); - var normal = triager(tokenizer(line)); + var normal = triager(tokenize(line)); delete normal.tokens; delete normal.indent; assert(sortedJsonCompare(normal, ret), 'fast path: ' + dump(normal) + '\n vs \n' + dump(ret)); @@ -1124,7 +1131,7 @@ function intertyper(lines, sidePass, baseLineNums) { //var time = Date.now(); - var t = tokenizer(line); + var t = tokenize(line.lineText, line.lineNum); item = triager(t); /* diff --git a/src/jsifier.js b/src/jsifier.js index 0e5f8ef3..f638ea08 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -234,8 +234,8 @@ function JSify(data, functionsOnly, givenFunctions) { function globalVariableHandler(item) { function needsPostSet(value) { if (typeof value !== 'string') return false; - return value[0] in UNDERSCORE_OPENPARENS || value.substr(0, 14) === 'CHECK_OVERFLOW' - || value.substr(0, 6) === 'GLOBAL'; + // (' is ok, as it is something we can indexize later into a concrete int: ('{{ FI_ ... + return /^([(_][^']|CHECK_OVERFLOW|GLOBAL).*/.test(value); } item.intertype = 'GlobalVariableStub'; @@ -308,6 +308,8 @@ function JSify(data, functionsOnly, givenFunctions) { JS: makeSetValue(makeGlobalUse(item.ident), i, value, structTypes[i], false, true) + ';' // ignore=true, since e.g. rtti and statics cause lots of safe_heap errors }); constant[i] = '0'; + } else { + if (typeof value === 'string') constant[i] = deParenCarefully(value); } }); @@ -405,6 +407,9 @@ function JSify(data, functionsOnly, givenFunctions) { var snippet = LibraryManager.library[ident]; var redirectedIdent = null; var deps = LibraryManager.library[ident + '__deps'] || []; + deps.forEach(function(dep) { + if (typeof snippet === 'string' && !(dep in LibraryManager.library)) warn('missing library dependency ' + dep + ', make sure you are compiling with the right options (see #ifdefs in src/library*.js)'); + }); var isFunction = false; if (typeof snippet === 'string') { @@ -536,7 +541,6 @@ function JSify(data, functionsOnly, givenFunctions) { case 'unreachable': line.JS = unreachableHandler(line); break; default: throw 'what is this line? ' + dump(line); } - assert(line.JS); //if (ASM_JS) assert(line.JS.indexOf('var ') < 0, dump(line)); if (line.assignTo) makeAssign(line); Framework.currItem = null; @@ -1361,7 +1365,7 @@ function JSify(data, functionsOnly, givenFunctions) { if (item.allocatedSize === 0) return ''; // This will not actually be shown - it's nativized return asmCoercion(getFastValue('sp', '+', item.allocatedIndex.toString()), 'i32'); } else { - return RuntimeGenerator.stackAlloc(getFastValue(calcAllocatedSize(item.allocatedType), '*', item.allocatedNum)); + return RuntimeGenerator.stackAlloc(getFastValue(calcAllocatedSize(item.allocatedType), '*', item.ident)); } } function va_argHandler(item) { diff --git a/src/library.js b/src/library.js index 5e71b087..875d8bab 100644 --- a/src/library.js +++ b/src/library.js @@ -4296,16 +4296,16 @@ LibraryManager.library = { }, llvm_trap: function() { - throw 'trap! ' + new Error().stack; + abort('trap!'); }, __assert_fail: function(condition, filename, line, func) { ABORT = true; - throw 'Assertion failed: ' + Pointer_stringify(condition) + ', at: ' + [filename ? Pointer_stringify(filename) : 'unknown filename', line, func ? Pointer_stringify(func) : 'unknown function'] + ' at ' + new Error().stack; + throw 'Assertion failed: ' + Pointer_stringify(condition) + ', at: ' + [filename ? Pointer_stringify(filename) : 'unknown filename', line, func ? Pointer_stringify(func) : 'unknown function'] + ' at ' + stackTrace(); }, __assert_func: function(filename, line, func, condition) { - throw 'Assertion failed: ' + (condition ? Pointer_stringify(condition) : 'unknown condition') + ', at: ' + [filename ? Pointer_stringify(filename) : 'unknown filename', line, func ? Pointer_stringify(func) : 'unknown function'] + ' at ' + new Error().stack; + throw 'Assertion failed: ' + (condition ? Pointer_stringify(condition) : 'unknown condition') + ', at: ' + [filename ? Pointer_stringify(filename) : 'unknown filename', line, func ? Pointer_stringify(func) : 'unknown function'] + ' at ' + stackTrace(); }, __cxa_guard_acquire: function(variable) { @@ -4353,7 +4353,7 @@ LibraryManager.library = { ___cxa_throw.initialized = true; } #if EXCEPTION_DEBUG - Module.printErr('Compiled code throwing an exception, ' + [ptr,type,destructor] + ', at ' + new Error().stack); + Module.printErr('Compiled code throwing an exception, ' + [ptr,type,destructor] + ', at ' + stackTrace()); #endif {{{ makeSetValue('_llvm_eh_exception.buf', '0', 'ptr', 'void*') }}} {{{ makeSetValue('_llvm_eh_exception.buf', QUANTUM_SIZE, 'type', 'void*') }}} diff --git a/src/library_browser.js b/src/library_browser.js index cba8ecdf..dd60a581 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -749,6 +749,7 @@ mergeInto(LibraryManager.library, { if (e instanceof ExitStatus) { return; } else { + if (e && typeof e === 'object' && e.stack) Module.printErr('exception thrown: ' + [e, e.stack]); throw e; } } diff --git a/src/library_fs.js b/src/library_fs.js index dc5c20f8..da1a4e7f 100644 --- a/src/library_fs.js +++ b/src/library_fs.js @@ -1,5 +1,5 @@ mergeInto(LibraryManager.library, { - $FS__deps: ['$ERRNO_CODES', '$ERRNO_MESSAGES', '__setErrNo', '$VFS', '$PATH', '$TTY', '$MEMFS', '$IDBFS', '$NODEFS', 'stdin', 'stdout', 'stderr', 'fflush'], + $FS__deps: ['$ERRNO_CODES', '$ERRNO_MESSAGES', '__setErrNo', '$PATH', '$TTY', '$MEMFS', '$IDBFS', '$NODEFS', 'stdin', 'stdout', 'stderr', 'fflush'], $FS__postset: 'FS.staticInit();' + '__ATINIT__.unshift({ func: function() { if (!Module["noFSInit"] && !FS.init.initialized) FS.init() } });' + '__ATMAIN__.push({ func: function() { FS.ignorePermissions = false } });' + @@ -27,24 +27,10 @@ mergeInto(LibraryManager.library, { // to modify the filesystem freely before run() is called. ignorePermissions: true, - ErrnoError: (function() { - function ErrnoError(errno) { - this.errno = errno; - for (var key in ERRNO_CODES) { - if (ERRNO_CODES[key] === errno) { - this.code = key; - break; - } - } - this.message = ERRNO_MESSAGES[errno]; - }; - ErrnoError.prototype = new Error(); - ErrnoError.prototype.constructor = ErrnoError; - return ErrnoError; - }()), + ErrnoError: null, // set during init handleFSError: function(e) { - if (!(e instanceof FS.ErrnoError)) throw e + ' : ' + new Error().stack; + if (!(e instanceof FS.ErrnoError)) throw e + ' : ' + stackTrace(); return ___setErrNo(e.errno); }, @@ -1091,6 +1077,20 @@ mergeInto(LibraryManager.library, { assert(!FS.init.initialized, 'FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)'); FS.init.initialized = true; + FS.ErrnoError = function ErrnoError(errno) { + this.errno = errno; + for (var key in ERRNO_CODES) { + if (ERRNO_CODES[key] === errno) { + this.code = key; + break; + } + } + this.message = ERRNO_MESSAGES[errno]; + this.stack = stackTrace(); + }; + FS.ErrnoError.prototype = new Error(); + FS.ErrnoError.prototype.constructor = FS.ErrnoError; + // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here Module['stdin'] = input || Module['stdin']; Module['stdout'] = output || Module['stdout']; diff --git a/src/library_gl.js b/src/library_gl.js index 511709ae..1ea8efc2 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -374,6 +374,9 @@ var LibraryGL = { }, #endif + // In WebGL, extensions must be explicitly enabled to be active, see http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.14 + // In GLES2, all extensions are enabled by default without additional operations. Init all extensions we need to give to GLES2 user + // code here, so that GLES2 code can operate without changing behavior. initExtensions: function() { if (GL.initExtensions.done) return; GL.initExtensions.done = true; @@ -394,6 +397,7 @@ var LibraryGL = { GL.generateTempBuffers(); #endif + // Detect the presence of a few extensions manually, this GL interop layer itself will need to know if they exist. GL.compressionExt = Module.ctx.getExtension('WEBGL_compressed_texture_s3tc') || Module.ctx.getExtension('MOZ_WEBGL_compressed_texture_s3tc') || Module.ctx.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc'); @@ -404,12 +408,80 @@ var LibraryGL = { GL.floatExt = Module.ctx.getExtension('OES_texture_float'); - GL.elementIndexUintExt = Module.ctx.getExtension('OES_element_index_uint'); - GL.standardDerivativesExt = Module.ctx.getExtension('OES_standard_derivatives'); + // These are the 'safe' feature-enabling extensions that don't add any performance impact related to e.g. debugging, and + // should be enabled by default so that client GLES2/GL code will not need to go through extra hoops to get its stuff working. + // As new extensions are ratified at http://www.khronos.org/registry/webgl/extensions/ , feel free to add your new extensions + // here, as long as they don't produce a performance impact for users that might not be using those extensions. + // E.g. debugging-related extensions should probably be off by default. + var automaticallyEnabledExtensions = [ "OES_texture_float", "OES_texture_half_float", "OES_standard_derivatives", + "OES_vertex_array_object", "WEBGL_compressed_texture_s3tc", "WEBGL_depth_texture", + "OES_element_index_uint", "EXT_texture_filter_anisotropic", "ANGLE_instanced_arrays", + "OES_texture_float_linear", "OES_texture_half_float_linear", "WEBGL_compressed_texture_atc", + "WEBGL_compressed_texture_pvrtc", "EXT_color_buffer_half_float", "WEBGL_color_buffer_float", + "EXT_frag_depth", "EXT_sRGB", "WEBGL_draw_buffers", "WEBGL_shared_resources" ]; + + function shouldEnableAutomatically(extension) { + for(var i in automaticallyEnabledExtensions) { + var include = automaticallyEnabledExtensions[i]; + if (ext.indexOf(include) != -1) { + return true; + } + } + return false; + } + + var extensions = Module.ctx.getSupportedExtensions(); + for(var e in extensions) { + var ext = extensions[e].replace('MOZ_', '').replace('WEBKIT_', ''); + if (automaticallyEnabledExtensions.indexOf(ext) != -1) { + Module.ctx.getExtension(ext); // Calling .getExtension enables that extension permanently, no need to store the return value to be enabled. + } + } + }, - GL.depthTextureExt = Module.ctx.getExtension("WEBGL_depth_texture") || - Module.ctx.getExtension("MOZ_WEBGL_depth_texture") || - Module.ctx.getExtension("WEBKIT_WEBGL_depth_texture"); + // In WebGL, uniforms in a shader program are accessed through an opaque object type 'WebGLUniformLocation'. + // In GLES2, uniforms are accessed via indices. Therefore we must generate a mapping of indices -> WebGLUniformLocations + // to provide the client code the API that uses indices. + // This function takes a linked GL program and generates a mapping table for the program. + // NOTE: Populating the uniform table is performed eagerly at glLinkProgram time, so glLinkProgram should be considered + // to be a slow/costly function call. Calling glGetUniformLocation is relatively fast, since it is always a read-only + // lookup to the table populated in this function call. + populateUniformTable: function(program) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'populateUniformTable', 'program'); +#endif + var p = GL.programs[program]; + GL.uniformTable[program] = {}; + var ptable = GL.uniformTable[program]; + // A program's uniformTable maps the string name of an uniform to an integer location of that uniform. + // The global GL.uniforms map maps integer locations to WebGLUniformLocations. + var numUniforms = Module.ctx.getProgramParameter(p, Module.ctx.ACTIVE_UNIFORMS); + for (var i = 0; i < numUniforms; ++i) { + var u = Module.ctx.getActiveUniform(p, i); + + var name = u.name; + // Strip off any trailing array specifier we might have got, e.g. "[0]". + if (name.indexOf(']', name.length-1) !== -1) { + var ls = name.lastIndexOf('['); + name = name.slice(0, ls); + } + + // Optimize memory usage slightly: If we have an array of uniforms, e.g. 'vec3 colors[3];', then + // only store the string 'colors' in ptable, and 'colors[0]', 'colors[1]' and 'colors[2]' will be parsed as 'colors'+i. + // Note that for the GL.uniforms table, we still need to fetch the all WebGLUniformLocations for all the indices. + var loc = Module.ctx.getUniformLocation(p, name); + var id = GL.getNewId(GL.uniforms); + ptable[name] = [u.size, id]; + GL.uniforms[id] = loc; + + for (var j = 1; j < u.size; ++j) { + var n = name + '['+j+']'; + loc = Module.ctx.getUniformLocation(p, n); + id = GL.getNewId(GL.uniforms); + + GL.uniforms[id] = loc; + } + } } }, @@ -431,7 +503,13 @@ var LibraryGL = { case 0x1F02 /* GL_VERSION */: return allocate(intArrayFromString(Module.ctx.getParameter(name_)), 'i8', ALLOC_NORMAL); case 0x1F03 /* GL_EXTENSIONS */: - return allocate(intArrayFromString(Module.ctx.getSupportedExtensions().join(' ')), 'i8', ALLOC_NORMAL); + var exts = Module.ctx.getSupportedExtensions(); + var gl_exts = []; + for (i in exts) { + gl_exts.push(exts[i]); + gl_exts.push("GL_" + exts[i]); + } + return allocate(intArrayFromString(gl_exts.join(' ')), 'i8', ALLOC_NORMAL); // XXX this leaks! TODO: Cache all results like this in library_gl.js to be clean and nice and avoid leaking. case 0x8B8C /* GL_SHADING_LANGUAGE_VERSION */: return allocate(intArrayFromString('OpenGL ES GLSL 1.00 (WebGL)'), 'i8', ALLOC_NORMAL); default: @@ -811,6 +889,7 @@ var LibraryGL = { glGetUniformfv: function(program, location, params) { #if GL_ASSERTIONS GL.validateGLObjectID(GL.programs, program, 'glGetUniformfv', 'program'); + GL.validateGLObjectID(GL.uniforms, location, 'glGetUniformfv', 'location'); #endif var data = Module.ctx.getUniform(GL.programs[program], GL.uniforms[location]); if (typeof data == 'number') { @@ -826,6 +905,7 @@ var LibraryGL = { glGetUniformiv: function(program, location, params) { #if GL_ASSERTIONS GL.validateGLObjectID(GL.programs, program, 'glGetUniformiv', 'program'); + GL.validateGLObjectID(GL.uniforms, location, 'glGetUniformiv', 'location'); #endif var data = Module.ctx.getUniform(GL.programs[program], GL.uniforms[location]); if (typeof data == 'number' || typeof data == 'boolean') { @@ -843,16 +923,31 @@ var LibraryGL = { GL.validateGLObjectID(GL.programs, program, 'glGetUniformLocation', 'program'); #endif name = Pointer_stringify(name); + + var arrayOffset = 0; + // If user passed an array accessor "[index]", parse the array index off the accessor. + if (name.indexOf(']', name.length-1) !== -1) { + var ls = name.lastIndexOf('['); + var arrayIndex = name.slice(ls+1, -1); + if (arrayIndex.length > 0) { + arrayOffset = parseInt(arrayIndex); + if (arrayOffset < 0) { + return -1; + } + } + name = name.slice(0, ls); + } + var ptable = GL.uniformTable[program]; - if (!ptable) ptable = GL.uniformTable[program] = {}; - var id = ptable[name]; - if (id) return id; - var loc = Module.ctx.getUniformLocation(GL.programs[program], name); - if (!loc) return -1; - id = GL.getNewId(GL.uniforms); - GL.uniforms[id] = loc; - ptable[name] = id; - return id; + if (!ptable) { + return -1; + } + var uniformInfo = ptable[name]; // returns pair [ dimension_of_uniform_array, uniform_location ] + if (uniformInfo && arrayOffset < uniformInfo[0]) { // Check if user asked for an out-of-bounds element, i.e. for 'vec4 colors[3];' user could ask for 'colors[10]' which should return -1. + return uniformInfo[1]+arrayOffset; + } else { + return -1; + } }, glGetVertexAttribfv__sig: 'viii', @@ -923,54 +1018,81 @@ var LibraryGL = { glUniform1f__sig: 'vif', glUniform1f: function(location, v0) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniform1f', 'location'); +#endif location = GL.uniforms[location]; Module.ctx.uniform1f(location, v0); }, glUniform2f__sig: 'viff', glUniform2f: function(location, v0, v1) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniform2f', 'location'); +#endif location = GL.uniforms[location]; Module.ctx.uniform2f(location, v0, v1); }, glUniform3f__sig: 'vifff', glUniform3f: function(location, v0, v1, v2) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniform3f', 'location'); +#endif location = GL.uniforms[location]; Module.ctx.uniform3f(location, v0, v1, v2); }, glUniform4f__sig: 'viffff', glUniform4f: function(location, v0, v1, v2, v3) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniform4f', 'location'); +#endif location = GL.uniforms[location]; Module.ctx.uniform4f(location, v0, v1, v2, v3); }, glUniform1i__sig: 'vii', glUniform1i: function(location, v0) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniform1i', 'location'); +#endif location = GL.uniforms[location]; Module.ctx.uniform1i(location, v0); }, glUniform2i__sig: 'viii', glUniform2i: function(location, v0, v1) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniform2i', 'location'); +#endif location = GL.uniforms[location]; Module.ctx.uniform2i(location, v0, v1); }, glUniform3i__sig: 'viiii', glUniform3i: function(location, v0, v1, v2) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniform3i', 'location'); +#endif location = GL.uniforms[location]; Module.ctx.uniform3i(location, v0, v1, v2); }, glUniform4i__sig: 'viiiii', glUniform4i: function(location, v0, v1, v2, v3) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniform4i', 'location'); +#endif location = GL.uniforms[location]; Module.ctx.uniform4i(location, v0, v1, v2, v3); }, glUniform1iv__sig: 'viii', glUniform1iv: function(location, count, value) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniform1iv', 'location'); +#endif location = GL.uniforms[location]; value = {{{ makeHEAPView('32', 'value', 'value+count*4') }}}; Module.ctx.uniform1iv(location, value); @@ -978,6 +1100,9 @@ var LibraryGL = { glUniform2iv__sig: 'viii', glUniform2iv: function(location, count, value) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniform2iv', 'location'); +#endif location = GL.uniforms[location]; count *= 2; value = {{{ makeHEAPView('32', 'value', 'value+count*4') }}}; @@ -986,6 +1111,9 @@ var LibraryGL = { glUniform3iv__sig: 'viii', glUniform3iv: function(location, count, value) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniform3iv', 'location'); +#endif location = GL.uniforms[location]; count *= 3; value = {{{ makeHEAPView('32', 'value', 'value+count*4') }}}; @@ -994,6 +1122,9 @@ var LibraryGL = { glUniform4iv__sig: 'viii', glUniform4iv: function(location, count, value) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniform4iv', 'location'); +#endif location = GL.uniforms[location]; count *= 4; value = {{{ makeHEAPView('32', 'value', 'value+count*4') }}}; @@ -1002,6 +1133,9 @@ var LibraryGL = { glUniform1fv__sig: 'viii', glUniform1fv: function(location, count, value) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniform1fv', 'location'); +#endif location = GL.uniforms[location]; var view; if (count == 1) { @@ -1016,6 +1150,9 @@ var LibraryGL = { glUniform2fv__sig: 'viii', glUniform2fv: function(location, count, value) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniform2fv', 'location'); +#endif location = GL.uniforms[location]; var view; if (count == 1) { @@ -1031,6 +1168,9 @@ var LibraryGL = { glUniform3fv__sig: 'viii', glUniform3fv: function(location, count, value) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniform3fv', 'location'); +#endif location = GL.uniforms[location]; var view; if (count == 1) { @@ -1047,6 +1187,9 @@ var LibraryGL = { glUniform4fv__sig: 'viii', glUniform4fv: function(location, count, value) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniform4fv', 'location'); +#endif location = GL.uniforms[location]; var view; if (count == 1) { @@ -1064,6 +1207,9 @@ var LibraryGL = { glUniformMatrix2fv__sig: 'viiii', glUniformMatrix2fv: function(location, count, transpose, value) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniformMatrix2fv', 'location'); +#endif location = GL.uniforms[location]; var view; if (count == 1) { @@ -1080,6 +1226,9 @@ var LibraryGL = { glUniformMatrix3fv__sig: 'viiii', glUniformMatrix3fv: function(location, count, transpose, value) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniformMatrix3fv', 'location'); +#endif location = GL.uniforms[location]; var view; if (count == 1) { @@ -1096,6 +1245,9 @@ var LibraryGL = { glUniformMatrix4fv__sig: 'viiii', glUniformMatrix4fv: function(location, count, transpose, value) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.uniforms, location, 'glUniformMatrix4fv', 'location'); +#endif location = GL.uniforms[location]; var view; if (count == 1) { @@ -1340,6 +1492,7 @@ var LibraryGL = { #endif Module.ctx.linkProgram(GL.programs[program]); GL.uniformTable[program] = {}; // uniforms no longer keep the same names after linking + GL.populateUniformTable(program); }, glGetProgramInfoLog__sig: 'viiii', @@ -4320,6 +4473,7 @@ var LibraryGL = { return 1 /* GL_TRUE */; }, + gluOrtho2D__deps: ['glOrtho'], gluOrtho2D: function(left, right, bottom, top) { _glOrtho(left, right, bottom, top, -1, 1); }, diff --git a/src/library_glut.js b/src/library_glut.js index 60dc6540..2321486a 100644 --- a/src/library_glut.js +++ b/src/library_glut.js @@ -189,12 +189,12 @@ var LibraryGLUT = { } }, - onMouseButtonDown: function(event){ + onMouseButtonDown: function(event) { Browser.calculateMouseEvent(event); GLUT.buttons |= (1 << event['button']); - if(event.target == Module["canvas"] && GLUT.mouseFunc){ + if (event.target == Module["canvas"] && GLUT.mouseFunc) { try { event.target.setCapture(); } catch (e) {} @@ -204,21 +204,40 @@ var LibraryGLUT = { } }, - onMouseButtonUp: function(event){ + onMouseButtonUp: function(event) { Browser.calculateMouseEvent(event); GLUT.buttons &= ~(1 << event['button']); - if(GLUT.mouseFunc) { + if (GLUT.mouseFunc) { event.preventDefault(); GLUT.saveModifiers(event); Runtime.dynCall('viiii', GLUT.mouseFunc, [event['button'], 1/*GLUT_UP*/, Browser.mouseX, Browser.mouseY]); } }, + onMouseWheel: function(event) { + Browser.calculateMouseEvent(event); + + // cross-browser wheel delta + var e = window.event || event; // old IE support + var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail))); + + var button = 3; // wheel up + if (delta < 0) { + button = 4; // wheel down + } + + if (GLUT.mouseFunc) { + event.preventDefault(); + GLUT.saveModifiers(event); + Runtime.dynCall('viiii', GLUT.mouseFunc, [button, 0/*GLUT_DOWN*/, Browser.mouseX, Browser.mouseY]); + } + }, + // TODO add fullscreen API ala: // http://johndyer.name/native-fullscreen-javascript-api-plus-jquery-plugin/ - onFullScreenEventChange: function(event){ + onFullScreenEventChange: function(event) { var width; var height; if (document["fullScreen"] || document["mozFullScreen"] || document["webkitIsFullScreen"]) { @@ -279,6 +298,10 @@ var LibraryGLUT = { window.addEventListener("mousemove", GLUT.onMousemove, true); window.addEventListener("mousedown", GLUT.onMouseButtonDown, true); window.addEventListener("mouseup", GLUT.onMouseButtonUp, true); + // IE9, Chrome, Safari, Opera + window.addEventListener("mousewheel", GLUT.onMouseWheel, true); + // Firefox + window.addEventListener("DOMMouseScroll", GLUT.onMouseWheel, true); } Browser.resizeListeners.push(function(width, height) { @@ -298,6 +321,10 @@ var LibraryGLUT = { window.removeEventListener("mousemove", GLUT.onMousemove, true); window.removeEventListener("mousedown", GLUT.onMouseButtonDown, true); window.removeEventListener("mouseup", GLUT.onMouseButtonUp, true); + // IE9, Chrome, Safari, Opera + window.removeEventListener("mousewheel", GLUT.onMouseWheel, true); + // Firefox + window.removeEventListener("DOMMouseScroll", GLUT.onMouseWheel, true); } Module["canvas"].width = Module["canvas"].height = 1; } }); diff --git a/src/library_memfs.js b/src/library_memfs.js index 4e56d996..94fd767e 100644 --- a/src/library_memfs.js +++ b/src/library_memfs.js @@ -140,6 +140,7 @@ mergeInto(LibraryManager.library, { delete old_node.parent.contents[old_node.name]; old_node.name = new_name; new_dir.contents[new_name] = old_node; + old_node.parent = new_dir; }, unlink: function(parent, name) { delete parent.contents[name]; diff --git a/src/library_sdl.js b/src/library_sdl.js index 27f2c0da..e64f117f 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -1122,7 +1122,7 @@ var LibrarySDL = { var ret = SDL.makeSurface(diagonal, diagonal, srcData.flags, false, 'rotozoomSurface'); var dstData = SDL.surfaces[ret]; dstData.ctx.translate(diagonal / 2, diagonal / 2); - dstData.ctx.rotate(angle * Math.PI / 180); + dstData.ctx.rotate(-angle * Math.PI / 180); dstData.ctx.drawImage(srcData.canvas, -w / 2, -h / 2, w, h); return ret; }, diff --git a/src/modules.js b/src/modules.js index cc9ca549..76e5db11 100644 --- a/src/modules.js +++ b/src/modules.js @@ -285,11 +285,7 @@ var Functions = { } else { if (!singlePhase) return 'NO_INDEX'; // Should not index functions in post ret = this.indexedFunctions[ident]; - if (!ret) { - ret = this.nextIndex; - this.nextIndex += FUNCTION_POINTER_ALIGNMENT; - this.indexedFunctions[ident] = ret; - } + assert(ret); ret = ret.toString(); } if (SIDE_MODULE && sig) { // sig can be undefined for the GL library functions diff --git a/src/parseTools.js b/src/parseTools.js index addf0f21..e3b1df6d 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -280,7 +280,7 @@ function isFunctionType(type, out) { i--; } assert(argText); - return isFunctionDef({ text: argText, item: tokenize(argText.substr(1, argText.length-2), true) }, out); + return isFunctionDef({ text: argText, item: tokenize(argText.substr(1, argText.length-2)) }, out); } function getReturnType(type) { @@ -914,13 +914,13 @@ function parseI64Constant(str, legalized) { } function parseNumerical(value, type) { - if ((!type || type == 'double' || type == 'float') && (value.substr && value.substr(0,2) == '0x')) { + if ((!type || type === 'double' || type === 'float') && /^0x/.test(value)) { // Hexadecimal double value, as the llvm docs say, // "The one non-intuitive notation for constants is the hexadecimal form of floating point constants." value = IEEEUnHex(value); } else if (USE_TYPED_ARRAYS == 2 && isIllegalType(type)) { return value; // do not parseFloat etc., that can lead to loss of precision - } else if (value == 'null') { + } else if (value === 'null') { // NULL *is* 0, in C/C++. No JS null! (null == 0 is false, etc.) value = '0'; } else if (value === 'true') { @@ -930,7 +930,7 @@ function parseNumerical(value, type) { } if (isNumber(value)) { var ret = parseFloat(value); // will change e.g. 5.000000e+01 to 50 - if (type in Runtime.FLOAT_TYPES) { + if (type === 'double' || type === 'float') { if (value[0] === '-' && ret === 0) return '-.0'; // fix negative 0, toString makes it 0 if (!RUNNING_JS_OPTS) ret = asmEnsureFloat(ret, type); } @@ -2474,13 +2474,6 @@ function walkInterdata(item, pre, post, obj) { if (walkInterdata(item.params[i], pre, post, obj)) return true; } } - if (item.possibleVars) { // other attributes that might contain interesting data; here, variables - var box = { intertype: 'value', ident: '' }; - for (i = 0; i <= item.possibleVars.length; i++) { - box.ident = item[item.possibleVars[i]]; - if (walkInterdata(box, pre, post, obj)) return true; - } - } return post && post(item, originalObj, obj); } @@ -2500,7 +2493,6 @@ function walkAndModifyInterdata(item, pre) { if (repl = walkAndModifyInterdata(item.params[i], pre)) item.params[i] = repl; } } - // Ignore possibleVars because we can't replace them anyhow } function parseBlockAddress(segment) { @@ -2582,6 +2574,11 @@ function deParen(text) { return text; } +function deParenCarefully(text) { + if (text[0] === '(' && text.indexOf('(', 1) < 0 && text[text.length-1] === ')') return text.substr(1, text.length-2); + return text; +} + function addVariable(ident, type, funcData) { funcData = funcData || Framework.currItem.funcData; assert(type); diff --git a/src/postamble.js b/src/postamble.js index cd892733..d64fb220 100644 --- a/src/postamble.js +++ b/src/postamble.js @@ -96,6 +96,7 @@ Module['callMain'] = Module.callMain = function callMain(args) { Module['noExitRuntime'] = true; return; } else { + if (e && typeof e === 'object' && e.stack) Module.printErr('exception thrown: ' + [e, e.stack]); throw e; } } finally { @@ -179,7 +180,7 @@ function abort(text) { ABORT = true; EXITSTATUS = 1; - throw 'abort() at ' + (new Error().stack); + throw 'abort() at ' + stackTrace(); } Module['abort'] = Module.abort = abort; diff --git a/src/preamble.js b/src/preamble.js index 88aaff77..aedb0e7c 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -36,7 +36,7 @@ var SAFE_HEAP_ERRORS = 0; var ACCEPTABLE_SAFE_HEAP_ERRORS = 0; function SAFE_HEAP_ACCESS(dest, type, store, ignore, storeValue) { - //if (dest === A_NUMBER) Module.print ([dest, type, store, ignore, storeValue] + ' ' + new Error().stack); // Something like this may be useful, in debugging + //if (dest === A_NUMBER) Module.print ([dest, type, store, ignore, storeValue] + ' ' + stackTrace()); // Something like this may be useful, in debugging assert(dest > 0, 'segmentation fault'); @@ -63,7 +63,7 @@ function SAFE_HEAP_ACCESS(dest, type, store, ignore, storeValue) { try { if (HEAP[dest].toString() === 'NaN') error = false; // NaN is acceptable, as a double value } catch(e){} - if (error) throw('Warning: Reading an invalid value at ' + dest + ' :: ' + new Error().stack + '\n'); + if (error) throw('Warning: Reading an invalid value at ' + dest + ' :: ' + stackTrace() + '\n'); } #endif if (type === null) return; @@ -72,14 +72,14 @@ function SAFE_HEAP_ACCESS(dest, type, store, ignore, storeValue) { if (!ignore) assert(history, 'Must have a history for a safe heap load! ' + dest + ':' + type); // Warning - bit fields in C structs cause loads+stores for each store, so // they will show up here... -// assert((history && history[0]) /* || HEAP[dest] === 0 */, "Loading from where there was no store! " + dest + ',' + HEAP[dest] + ',' + type + ', \n\n' + new Error().stack + '\n'); +// assert((history && history[0]) /* || HEAP[dest] === 0 */, "Loading from where there was no store! " + dest + ',' + HEAP[dest] + ',' + type + ', \n\n' + stackTrace() + '\n'); // if (history[0].type !== type) { if (history !== type && !ignore) { Module.print('Load-store consistency assumption failure! ' + dest); Module.print('\n'); Module.print(JSON.stringify(history)); Module.print('\n'); - Module.print('LOAD: ' + type + ', ' + new Error().stack); + Module.print('LOAD: ' + type + ', ' + stackTrace()); Module.print('\n'); SAFE_HEAP_ERRORS++; assert(SAFE_HEAP_ERRORS <= ACCEPTABLE_SAFE_HEAP_ERRORS, 'Load-store consistency assumption failure!'); @@ -93,9 +93,9 @@ function SAFE_HEAP_STORE(dest, value, type, ignore) { #endif if (!ignore && !value && (value === null || value === undefined)) { - throw('Warning: Writing an invalid value of ' + JSON.stringify(value) + ' at ' + dest + ' :: ' + new Error().stack + '\n'); + throw('Warning: Writing an invalid value of ' + JSON.stringify(value) + ' at ' + dest + ' :: ' + stackTrace() + '\n'); } - //if (!ignore && (value === Infinity || value === -Infinity || isNaN(value))) throw [value, typeof value, new Error().stack]; + //if (!ignore && (value === Infinity || value === -Infinity || isNaN(value))) throw [value, typeof value, stackTrace()]; SAFE_HEAP_ACCESS(dest, type, true, ignore, value); if (dest in HEAP_WATCHED) { @@ -640,6 +640,143 @@ function stringToUTF32(str, outPtr) { } Module['stringToUTF32'] = stringToUTF32; +function demangle(func) { + try { + if (typeof func === 'number') func = Pointer_stringify(func); + if (func[0] !== '_') return func; + if (func[1] !== '_') return func; // C function + if (func[2] !== 'Z') return func; + var i = 3; + // params, etc. + var basicTypes = { + 'v': 'void', + 'b': 'bool', + 'c': 'char', + 's': 'short', + 'i': 'int', + 'l': 'long', + 'f': 'float', + 'd': 'double', + 'w': 'wchar_t', + 'a': 'signed char', + 'h': 'unsigned char', + 't': 'unsigned short', + 'j': 'unsigned int', + 'm': 'unsigned long', + 'x': 'long long', + 'y': 'unsigned long long', + 'z': '...' + }; + function dump(x) { + //return; + if (x) Module.print(x); + Module.print(func); + var pre = ''; + for (var a = 0; a < i; a++) pre += ' '; + Module.print (pre + '^'); + } + var subs = []; + function parseNested() { + i++; + if (func[i] === 'K') i++; + var parts = []; + while (func[i] !== 'E') { + if (func[i] === 'S') { // substitution + i++; + var next = func.indexOf('_', i); + var num = func.substring(i, next) || 0; + parts.push(subs[num] || '?'); + i = next+1; + continue; + } + var size = parseInt(func.substr(i)); + var pre = size.toString().length; + if (!size || !pre) { i--; break; } // counter i++ below us + var curr = func.substr(i + pre, size); + parts.push(curr); + subs.push(curr); + i += pre + size; + } + i++; // skip E + return parts; + } + function parse(rawList, limit, allowVoid) { // main parser + limit = limit || Infinity; + var ret = '', list = []; + function flushList() { + return '(' + list.join(', ') + ')'; + } + var name; + if (func[i] !== 'N') { + // not namespaced + if (func[i] === 'K') i++; + var size = parseInt(func.substr(i)); + if (size) { + var pre = size.toString().length; + name = func.substr(i + pre, size); + i += pre + size; + } + } else { + // namespaced N-E + name = parseNested().join('::'); + limit--; + if (limit === 0) return rawList ? [name] : name; + } + if (func[i] === 'I') { + i++; + var iList = parse(true); + var iRet = parse(true, 1, true); + ret += iRet[0] + ' ' + name + '<' + iList.join(', ') + '>'; + } else { + ret = name; + } + paramLoop: while (i < func.length && limit-- > 0) { + //dump('paramLoop'); + var c = func[i++]; + if (c in basicTypes) { + list.push(basicTypes[c]); + } else { + switch (c) { + case 'P': list.push(parse(true, 1, true)[0] + '*'); break; // pointer + case 'R': list.push(parse(true, 1, true)[0] + '&'); break; // reference + case 'L': { // literal + i++; // skip basic type + var end = func.indexOf('E', i); + var size = end - i; + list.push(func.substr(i, size)); + i += size + 2; // size + 'EE' + break; + } + case 'A': { // array + var size = parseInt(func.substr(i)); + i += size.toString().length; + if (func[i] !== '_') throw '?'; + i++; // skip _ + list.push(parse(true, 1, true)[0] + ' [' + size + ']'); + break; + } + case 'E': break paramLoop; + default: ret += '?' + c; break paramLoop; + } + } + } + if (!allowVoid && list.length === 1 && list[0] === 'void') list = []; // avoid (void) + return rawList ? list : ret + flushList(); + } + return parse(); + } catch(e) { + return func; + } +} + +function demangleAll(text) { + return text.replace(/__Z[\w\d_]+/g, function(x) { var y = demangle(x); return x === y ? x : (x + ' [' + y + ']') }); +} + +function stackTrace() { + return demangleAll(new Error().stack); +} + // Memory management var PAGE_SIZE = 4096; diff --git a/tests/cases/phiptrtoint.ll b/tests/cases/phiptrtoint.ll new file mode 100644 index 00000000..d682dc06 --- /dev/null +++ b/tests/cases/phiptrtoint.ll @@ -0,0 +1,138 @@ +; ModuleID = '/tmp/tmpJctwj0/bug.bc' +; just an asm validation check, no output +target datalayout = "e-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-p:32:32:32-v128:32:32" +target triple = "le32-unknown-nacl" + +%"class.test::Processor" = type { i32, %"class.test::StateMachine" } +%"class.test::StateMachine" = type { { i32, i32 } } + +@_ZN4test9ProcessorC1Ev = alias internal void (%"class.test::Processor"*)* @_ZN4test9ProcessorC2Ev +@_ZN4test9ProcessorD1Ev = alias internal void (%"class.test::Processor"*)* @_ZN4test9ProcessorD2Ev + +define internal void @_ZN4test9ProcessorC2Ev(%"class.test::Processor"* nocapture %this) unnamed_addr nounwind align 2 { + %1 = getelementptr inbounds %"class.test::Processor"* %this, i32 0, i32 0 + store i32 0, i32* %1, align 4 + %2 = getelementptr inbounds %"class.test::Processor"* %this, i32 0, i32 1, i32 0 + store { i32, i32 } zeroinitializer, { i32, i32 }* %2, align 4 + ret void +} + +define internal void @_ZN4test9ProcessorD2Ev(%"class.test::Processor"* nocapture %this) unnamed_addr nounwind readnone align 2 { + ret void +} + +define internal zeroext i1 @_ZN4test9Processor16handleFirstStateEv(%"class.test::Processor"* nocapture %this) align 2 { + %1 = tail call i32 @rand() + %2 = getelementptr inbounds %"class.test::Processor"* %this, i32 0, i32 0 + %3 = load i32* %2, align 4 + %4 = add nsw i32 %3, %1 + store i32 %4, i32* %2, align 4 + %5 = and i32 %4, 1 + %6 = icmp eq i32 %5, 0 + ret i1 %6 +} + +declare i32 @rand() + +define internal zeroext i1 @_ZN4test9Processor15handleLastStateEv(%"class.test::Processor"* nocapture %this) align 2 { + %1 = tail call i32 @rand() + %2 = getelementptr inbounds %"class.test::Processor"* %this, i32 0, i32 0 + %3 = load i32* %2, align 4 + %4 = add nsw i32 %3, %1 + store i32 %4, i32* %2, align 4 + ret i1 true +} + +define internal zeroext i1 @_ZN4test9Processor3runEv(%"class.test::Processor"* %this) align 2 { + %1 = getelementptr inbounds %"class.test::Processor"* %this, i32 0, i32 1, i32 0 + store { i32, i32 } { i32 ptrtoint (i1 (%"class.test::Processor"*)* @_ZN4test9Processor16handleFirstStateEv to i32), i32 0 }, { i32, i32 }* %1, align 4 + %2 = bitcast %"class.test::Processor"* %this to i8* + br label %.backedge + +.backedge: ; preds = %25, %..backedge_crit_edge, %0 + %3 = phi { i32, i32 } [ { i32 ptrtoint (i1 (%"class.test::Processor"*)* @_ZN4test9Processor16handleFirstStateEv to i32), i32 0 }, %0 ], [ %.pre.pre, %..backedge_crit_edge ], [ { i32 ptrtoint (i1 (%"class.test::Processor"*)* @_ZN4test9Processor15handleLastStateEv to i32), i32 0 }, %25 ] + %.fca.0.extract = extractvalue { i32, i32 } %3, 0 + %.fca.1.extract = extractvalue { i32, i32 } %3, 1 + %4 = icmp ne i32 %.fca.0.extract, ptrtoint (i1 (%"class.test::Processor"*)* @_ZN4test9Processor15handleLastStateEv to i32) + %5 = icmp ne i32 %.fca.0.extract, 0 + %6 = icmp ne i32 %.fca.1.extract, 0 + %7 = and i1 %5, %6 + %8 = or i1 %4, %7 + %9 = getelementptr inbounds i8* %2, i32 %.fca.1.extract + %10 = bitcast i8* %9 to %"class.test::Processor"* + %11 = and i32 %.fca.0.extract, 1 + %12 = icmp eq i32 %11, 0 + br i1 %12, label %20, label %13 + +; <label>:13 ; preds = %.backedge + %14 = bitcast i8* %9 to i8** + %15 = load i8** %14, align 4 + %16 = add i32 %.fca.0.extract, -1 + %17 = getelementptr i8* %15, i32 %16 + %18 = bitcast i8* %17 to i1 (%"class.test::Processor"*)** + %19 = load i1 (%"class.test::Processor"*)** %18, align 4 + br label %_ZN4test12StateMachineINS_9ProcessorEE11handleStateEPS1_.exit + +; <label>:20 ; preds = %.backedge + %21 = inttoptr i32 %.fca.0.extract to i1 (%"class.test::Processor"*)* + br label %_ZN4test12StateMachineINS_9ProcessorEE11handleStateEPS1_.exit + +_ZN4test12StateMachineINS_9ProcessorEE11handleStateEPS1_.exit: ; preds = %20, %13 + %22 = phi i1 (%"class.test::Processor"*)* [ %19, %13 ], [ %21, %20 ] + %23 = tail call zeroext i1 %22(%"class.test::Processor"* %10) + br i1 %8, label %24, label %26 + +; <label>:24 ; preds = %_ZN4test12StateMachineINS_9ProcessorEE11handleStateEPS1_.exit + br i1 %23, label %25, label %..backedge_crit_edge + +..backedge_crit_edge: ; preds = %24 + %.pre.pre = load { i32, i32 }* %1, align 4 + br label %.backedge + +; <label>:25 ; preds = %24 + store { i32, i32 } { i32 ptrtoint (i1 (%"class.test::Processor"*)* @_ZN4test9Processor15handleLastStateEv to i32), i32 0 }, { i32, i32 }* %1, align 4 + br label %.backedge + +; <label>:26 ; preds = %_ZN4test12StateMachineINS_9ProcessorEE11handleStateEPS1_.exit + ret i1 %23 +} + +define internal i32 @_ZNK4test9Processor6resultEv(%"class.test::Processor"* nocapture %this) nounwind readonly align 2 { + %1 = getelementptr inbounds %"class.test::Processor"* %this, i32 0, i32 0 + %2 = load i32* %1, align 4 + ret i32 %2 +} + +define i32 @runProcess() { + %processor = alloca %"class.test::Processor", align 4 + call void @_ZN4test9ProcessorC1Ev(%"class.test::Processor"* %processor) + %1 = invoke zeroext i1 @_ZN4test9Processor3runEv(%"class.test::Processor"* %processor) + to label %2 unwind label %5 + +; <label>:2 ; preds = %0 + %3 = invoke i32 @_ZNK4test9Processor6resultEv(%"class.test::Processor"* %processor) + to label %4 unwind label %5 + +; <label>:4 ; preds = %2 + call void @_ZN4test9ProcessorD1Ev(%"class.test::Processor"* %processor) + ret i32 %3 + +; <label>:5 ; preds = %2, %0 + %6 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) + cleanup + invoke void @_ZN4test9ProcessorD1Ev(%"class.test::Processor"* %processor) + to label %7 unwind label %8 + +; <label>:7 ; preds = %5 + resume { i8*, i32 } %6 + +; <label>:8 ; preds = %5 + %9 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) + catch i8* null + call void @_ZSt9terminatev() noreturn nounwind + unreachable +} + +declare i32 @__gxx_personality_v0(...) + +declare void @_ZSt9terminatev() diff --git a/tests/cases/phiptrtoint.txt b/tests/cases/phiptrtoint.txt new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/cases/phiptrtoint.txt diff --git a/tests/cases/sub_11_0.ll b/tests/cases/sub_11_0.ll new file mode 100644 index 00000000..7f0bb285 --- /dev/null +++ b/tests/cases/sub_11_0.ll @@ -0,0 +1,16 @@ +; ModuleID = 'tests/hello_world.bc' + +@.str = private unnamed_addr constant [15 x i8] c"hello, world!\0A\00", align 1 ; [#uses=1 type=[15 x i8]*] + +; [#uses=0] +define i32 @main() { +entry: + %retval = alloca i32, align 4 ; [#uses=1 type=i32*] + %0 = sub nsw i32 1, 1 + store i32 %0, i32* %retval + %call = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([15 x i8]* @.str, i32 0, i32 0)) ; [#uses=0 type=i32] + ret i32 1 +} + +; [#uses=1] +declare i32 @printf(i8*, ...) diff --git a/tests/gles2_uniform_arrays.cpp b/tests/gles2_uniform_arrays.cpp new file mode 100644 index 00000000..84e394dc --- /dev/null +++ b/tests/gles2_uniform_arrays.cpp @@ -0,0 +1,121 @@ +#include "SDL/SDL_opengl.h" +#include "SDL/SDL.h" + +#include <stdio.h> +#include <string.h> +#include <assert.h> + +void RunTest(int testVariant) +{ + GLuint vs = 0; + + const char *vsCode = "#version 100\n" + "attribute vec4 pos; void main() { gl_Position = pos; }"; + + vs = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vs, 1, &vsCode, NULL); + glCompileShader(vs); + + GLuint ps = 0; + + const char *psCode = "#version 100\n" + "precision lowp float;\n" + "uniform vec3 color;\n" + "uniform vec3 colors[3];\n" + "void main() { gl_FragColor = vec4(color,1) + vec4(colors[0].r, colors[1].g, colors[2].b, 1); }"; + + ps = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(ps, 1, &psCode, NULL); + glCompileShader(ps); + + GLuint program = 0; + program = glCreateProgram(); + glAttachShader(program, vs); + glAttachShader(program, ps); + glBindAttribLocation(program, 0, "pos"); + glLinkProgram(program); + + int color_loc = glGetUniformLocation(program, "color"); + assert(color_loc != -1); + + glUseProgram(program); + float col[3] = { 0.2f, 0.2f, 0.2f }; + glUniform3fv(color_loc, 1, col); + + int loc = glGetUniformLocation(program, "colors"); + assert(loc != -1); + // In previous Emscripten GL layer code, calling glGetUniformLocation would do extra caching operations that interacts how glUniform** after that will work, + // so to exhibit extra issues in old code (and to keep new code from regressing), must test both with and without excess glGetUniformLocation calls. + if ((testVariant&1) != 0) + { + // Deliberately check in odd order to make sure any kind of lazy operations won't affect the indices we get. + assert(glGetUniformLocation(program, "colors[2]") == loc+2); + assert(glGetUniformLocation(program, "colors[0]") == loc); + assert(glGetUniformLocation(program, "colors[3]") == -1); + assert(glGetUniformLocation(program, "colors[1]") == loc+1); + assert(glGetUniformLocation(program, "colors[]") == loc); + assert(glGetUniformLocation(program, "colors[-100]") == -1); + assert(glGetUniformLocation(program, "colors[bleh]") == -1); + } + + float colors[4*3] = { 1,0,0, 0,0.5,0, 0,0,0.2, 1,1,1 }; + + if ((testVariant&2)!=0) + { + glUniform3fv(loc+1, 3, colors+3); // Pass the actual colors (testing a nonzero location offset), but do a mistake by setting one index too many. Spec says this should be gracefully handled, and that excess elements are ignored. + assert(glGetError() == GL_NO_ERROR); + glUniform3fv(loc, 1, colors); // Set the first index as well. + assert(glGetError() == GL_NO_ERROR); + } + else + { + glUniform3fv(loc, 4, colors); // Just directly set the full array. + assert(glGetError() == GL_NO_ERROR); + } + + assert(glGetError() == GL_NO_ERROR); + + GLuint vbo = 0; + const GLfloat v[] = { -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1 }; + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(v), v, GL_STATIC_DRAW); + + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(GLfloat), 0); + glEnableVertexAttribArray(0); + + glDrawArrays(GL_TRIANGLES, 0, 6); + + unsigned char pixel[4]; + glReadPixels(1,1,1,1,GL_RGBA,GL_UNSIGNED_BYTE, pixel); + //printf("%d,%d,%d,%d\n", pixel[0], pixel[1], pixel[2], pixel[3]); + assert(pixel[0] == 255); + assert(pixel[1] == 178); + assert(pixel[2] == 102); + assert(pixel[3] == 255); + + printf("OK: Case %d passed.\n", testVariant); + // Lazy, don't clean up afterwards. +} + +int main(int argc, char *argv[]) +{ + SDL_Surface *screen; + + // Slightly different SDL initialization + if ( SDL_Init(SDL_INIT_VIDEO) != 0 ) { + printf("Unable to initialize SDL: %s\n", SDL_GetError()); + return 1; + } + + screen = SDL_SetVideoMode( 640, 480, 16, SDL_OPENGL ); // *changed* + if ( !screen ) { + printf("Unable to set video mode: %s\n", SDL_GetError()); + return 1; + } + + for(int i = 0; i < 4; ++i) + RunTest(i); + + return 0; +} diff --git a/tests/glut_wheelevents.c b/tests/glut_wheelevents.c new file mode 100644 index 00000000..7be01636 --- /dev/null +++ b/tests/glut_wheelevents.c @@ -0,0 +1,68 @@ +#include <stdio.h> +#include <stdlib.h> +#include <GL/glut.h> +#include <EGL/egl.h> +#include <emscripten.h> + +#define MULTILINE(...) #__VA_ARGS__ + +int wheel_up = 0; +int wheel_down = 0; + +int result = 0; + +void mouseCB(int button, int state, int x, int y) +{ + if(button == 3) + { + wheel_up = 1; + } + else if (button == 4) + { + wheel_down = 1; + } +} + +int main(int argc, char *argv[]) +{ + emscripten_run_script(MULTILINE( + Module.injectWheelEvent = function(x, y, delta) { + var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; + var event = document.createEvent("MouseEvents"); + if (!isFirefox) { + // mouse wheel event for IE9, Chrome, Safari, Opera + event.initMouseEvent('mousewheel', true, true, window, + 0, Module['canvas'].offsetLeft + x, Module['canvas'].offsetTop + y, Module['canvas'].offsetLeft + x, Module['canvas'].offsetTop + y, + 0, 0, 0, 0, 0, null); + event.wheelDelta = delta; + } else { + // mouse wheel event for Firefox, the delta sign is inversed for that browser and is stored in the detail property of the mouse event + event.initMouseEvent('DOMMouseScroll', true, true, window, + -delta, Module['canvas'].offsetLeft + x, Module['canvas'].offsetTop + y, Module['canvas'].offsetLeft + x, Module['canvas'].offsetTop + y, + 0, 0, 0, 0, 0, null); + } + Module['canvas'].dispatchEvent(event); + } + )); + + + glutInit(&argc, argv); + + glutMouseFunc(&mouseCB); + + // inject wheel up event (delta > 0) + emscripten_run_script("Module.injectWheelEvent(100, 100, 1)"); + if (wheel_up) { + printf("%s\n", "mouse wheel up event received"); + } + // inject wheel down event (delta < 0) + emscripten_run_script("Module.injectWheelEvent(100, 100, -1)"); + if (wheel_down) { + printf("%s\n", "mouse wheel down event received"); + } + + result = wheel_up && wheel_down; + + REPORT_RESULT(); + return 0; +} diff --git a/tests/sdl_rotozoom.c b/tests/sdl_rotozoom.c index cdbdcc6f..2c0d35df 100644 --- a/tests/sdl_rotozoom.c +++ b/tests/sdl_rotozoom.c @@ -38,7 +38,7 @@ int main(int argc, char **argv) { sprite[2] = zoomSurface(sprite[0], 0.5, 0.5, SMOOTHING_ON); sprite[3] = zoomSurface(sprite[1], 0.5, 0.5, SMOOTHING_ON); sprite[4] = rotozoomSurface(sprite[0], -20, 0.3, SMOOTHING_ON); - sprite[5] = rotozoomSurface(sprite[1], 45, 0.5, SMOOTHING_ON); + sprite[5] = rotozoomSurface(sprite[1], 20, 1, SMOOTHING_ON); sprite[6] = zoomSurface(sprite[0], -0.5, 0.5, SMOOTHING_ON); sprite[7] = zoomSurface(sprite[0], -0.5, -0.5, SMOOTHING_ON); sprite[8] = rotozoomSurface(sprite[1], 0, 0.5, SMOOTHING_ON); diff --git a/tests/sdl_rotozoom.png b/tests/sdl_rotozoom.png Binary files differindex 5933754f..ebde79f2 100644 --- a/tests/sdl_rotozoom.png +++ b/tests/sdl_rotozoom.png diff --git a/tests/stdio/test_rename.c b/tests/stdio/test_rename.c index f15c8140..1a5017c1 100644 --- a/tests/stdio/test_rename.c +++ b/tests/stdio/test_rename.c @@ -25,6 +25,8 @@ void setup() { mkdir("dir/subdir", 0777); mkdir("dir-readonly", 0555); mkdir("dir-nonempty", 0777); + mkdir("dir/subdir3", 0777); + mkdir("dir/subdir3/subdir3_1", 0777); create_file("dir-nonempty/file", "abcdef", 0777); } @@ -38,6 +40,9 @@ void cleanup() { rmdir("dir/subdir"); rmdir("dir/subdir1"); rmdir("dir/subdir2"); + rmdir("dir/subdir3/subdir3_1/subdir1 renamed"); + rmdir("dir/subdir3/subdir3_1"); + rmdir("dir/subdir3"); rmdir("dir"); rmdir("dir-readonly"); unlink("dir-nonempty/file"); @@ -96,6 +101,11 @@ void test() { err = access("dir/subdir2", F_OK); assert(!err); + err = rename("dir/subdir2", "dir/subdir3/subdir3_1/subdir1 renamed"); + assert(!err); + err = access("dir/subdir3/subdir3_1/subdir1 renamed", F_OK); + assert(!err); + puts("success"); } diff --git a/tests/test_browser.py b/tests/test_browser.py index 799759a1..ecd331fd 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -866,6 +866,9 @@ keydown(100);keyup(100); // trigger the end def test_glut_touchevents(self): self.btest('glut_touchevents.c', '1') + def test_glut_wheelevents(self): + self.btest('glut_wheelevents.c', '1') + def test_emscripten_get_now(self): self.btest('emscripten_get_now.cpp', '1') @@ -1308,6 +1311,10 @@ keydown(100);keyup(100); // trigger the end def test_gl_vertex_buffer(self): self.btest('gl_vertex_buffer.c', reference='gl_vertex_buffer.png', args=['-s', 'GL_UNSAFE_OPTS=0', '-s', 'LEGACY_GL_EMULATION=1'], reference_slack=1) + # Does not pass due to https://bugzilla.mozilla.org/show_bug.cgi?id=924264 so disabled for now. + # def test_gles2_uniform_arrays(self): + # self.btest('gles2_uniform_arrays.cpp', args=['-s', 'GL_ASSERTIONS=1'], expected=['1']) + def test_matrix_identity(self): self.btest('gl_matrix_identity.c', expected=['-1882984448', '460451840'], args=['-s', 'LEGACY_GL_EMULATION=1']) @@ -1385,7 +1392,7 @@ keydown(100);keyup(100); // trigger the end def test_sdl_rotozoom(self): shutil.copyfile(path_from_root('tests', 'screenshot.png'), os.path.join(self.get_dir(), 'screenshot.png')) - self.btest('sdl_rotozoom.c', reference='sdl_rotozoom.png', args=['--preload-file', 'screenshot.png'], reference_slack=5) + self.btest('sdl_rotozoom.c', reference='sdl_rotozoom.png', args=['--preload-file', 'screenshot.png']) def test_sdl_gfx_primitives(self): self.btest('sdl_gfx_primitives.c', reference='sdl_gfx_primitives.png', reference_slack=1) diff --git a/tests/test_core.py b/tests/test_core.py index c1bfce6f..87925082 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1073,7 +1073,6 @@ Succeeded! self.do_run(open(path_from_root('tests', 'cube2md5.cpp')).read(), open(path_from_root('tests', 'cube2md5.ok')).read()) def test_cube2hash(self): - try: old_chunk_size = os.environ.get('EMSCRIPT_MAX_CHUNK_SIZE') or '' os.environ['EMSCRIPT_MAX_CHUNK_SIZE'] = '1' # test splitting out each function to a chunk in emscripten.py (21 functions here) @@ -1091,6 +1090,17 @@ Succeeded! finally: os.environ['EMSCRIPT_MAX_CHUNK_SIZE'] = old_chunk_size + assert 'asm1' in test_modes + if self.run_name == 'asm1': + assert Settings.RELOOP + generated = open('src.cpp.o.js').read() + main = generated[generated.find('function _main'):] + main = main[:main.find('\n}')] + num_vars = 0 + for v in re.findall('var [^;]+;', main): + num_vars += v.count(',') + 1 + assert num_vars == 10, 'no variable elimination should have been run, but seeing %d' % num_vars + def test_unaligned(self): if Settings.QUANTUM_SIZE == 1: return self.skip('No meaning to unaligned addresses in q1') @@ -8608,9 +8618,10 @@ void*:16 assert 'asm1' in test_modes if self.run_name == 'asm1': - assert not Settings.RELOOP - Settings.RELOOP = 1 # check for mixing of relooping with asm1 - self.do_run(path_from_root('tests', 'cubescript'), '*\nTemp is 33\n9\n5\nhello, everyone\n*', main_file='command.cpp') + generated = open('src.cpp.o.js').read() + main = generated[generated.find('function runPostSets'):] + main = main[:main.find('\n}')] + assert main.count('\n') == 7, 'must not emit too many postSets: %d' % main.count('\n') def test_gcc_unmangler(self): Settings.NAMED_GLOBALS = 1 # test coverage for this @@ -8649,7 +8660,10 @@ void*:16 def test_freetype(self): if self.emcc_args is None: return self.skip('requires emcc') if Settings.QUANTUM_SIZE == 1: return self.skip('TODO: Figure out and try to fix') - if Settings.ASM_JS and '-O2' not in self.emcc_args: return self.skip('mozilla bug 863867') + + assert 'asm2g' in test_modes + if self.run_name == 'asm2g': + Settings.ALIASING_FUNCTION_POINTERS = 1 - Settings.ALIASING_FUNCTION_POINTERS # flip for some more coverage here if Settings.CORRECT_SIGNS == 0: Settings.CORRECT_SIGNS = 1 # Not sure why, but needed @@ -9462,6 +9476,32 @@ def process(filename): Settings.ALIASING_FUNCTION_POINTERS = 1 - Settings.ALIASING_FUNCTION_POINTERS # flip the test self.do_run(src, '''Hello 7 from JS!''') + def test_demangle_stacks(self): + if Settings.ASM_JS: return self.skip('spidermonkey has stack trace issues') + + src = r''' + #include<stdio.h> + #include<stdlib.h> + + namespace NameSpace { + class Class { + public: + int Aborter(double x, char y, int *z) { + int addr = x+y+(int)z; + void *p = (void*)addr; + for (int i = 0; i < 100; i++) free(p); // will abort, should show proper stack trace + } + }; + } + + int main(int argc, char **argv) { + NameSpace::Class c; + c.Aborter(1.234, 'a', NULL); + return 0; + } + ''' + self.do_run(src, 'NameSpace::Class::Aborter(double, char, int*)'); + def test_embind(self): if self.emcc_args is None: return self.skip('requires emcc') Building.COMPILER_TEST_OPTS += ['--bind'] diff --git a/tests/test_other.py b/tests/test_other.py index afad1927..185b4853 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -125,8 +125,6 @@ Options that are modified or new in %s include: (['-o', 'something.js', '-O0'], 0, None, 0, 0), (['-o', 'something.js', '-O1'], 1, None, 0, 0), (['-o', 'something.js', '-O1', '-g'], 1, None, 0, 0), # no closure since debug - (['-o', 'something.js', '-O1', '--closure', '1'], 1, None, 1, 0), - (['-o', 'something.js', '-O1', '--closure', '1', '-s', 'ASM_JS=0'], 1, None, 1, 0), (['-o', 'something.js', '-O2'], 2, None, 0, 1), (['-o', 'something.js', '-O2', '-g'], 2, None, 0, 0), (['-o', 'something.js', '-Os'], 2, None, 0, 1), @@ -169,9 +167,9 @@ Options that are modified or new in %s include: # closure has not been run, we can do some additional checks. TODO: figure out how to do these even with closure assert '._main = ' not in generated, 'closure compiler should not have been run' if keep_debug: - assert ('(label)' in generated or '(label | 0)' in generated) == (opt_level <= 1), 'relooping should be in opt >= 2' + assert ('(label)' in generated or '(label | 0)' in generated) == (opt_level <= 0), 'relooping should be in opt >= 1' assert ('assert(STACKTOP < STACK_MAX' in generated) == (opt_level == 0), 'assertions should be in opt == 0' - assert 'var $i;' in generated or 'var $i_0' in generated or 'var $storemerge3;' in generated or 'var $storemerge4;' in generated or 'var $i_04;' in generated or 'var $original = 0' in generated, 'micro opts should always be on' + assert 'var $i;' in generated or 'var $i_0' in generated or 'var $storemerge3;' in generated or 'var $storemerge4;' in generated or '$i_04' in generated or '$i_05' in generated or 'var $original = 0' in generated, 'micro opts should always be on' if opt_level >= 2 and '-g' in params: assert re.search('HEAP8\[\$?\w+ ?\+ ?\(+\$?\w+ ?', generated) or re.search('HEAP8\[HEAP32\[', generated), 'eliminator should create compound expressions, and fewer one-time vars' # also in -O1, but easier to test in -O2 assert ('_puts(' in generated) == (opt_level >= 1), 'with opt >= 1, llvm opts are run and they should optimize printf to puts' @@ -1647,6 +1645,8 @@ f.close() ['asm', 'outline']), (path_from_root('tools', 'test-js-optimizer-asm-outline3.js'), open(path_from_root('tools', 'test-js-optimizer-asm-outline3-output.js')).read(), ['asm', 'outline']), + (path_from_root('tools', 'test-js-optimizer-asm-minlast.js'), open(path_from_root('tools', 'test-js-optimizer-asm-minlast-output.js')).read(), + ['asm', 'minifyWhitespace', 'last']), ]: print input output = Popen(listify(NODE_JS) + [path_from_root('tools', 'js-optimizer.js'), input] + passes, stdin=PIPE, stdout=PIPE).communicate()[0] @@ -1666,20 +1666,16 @@ f.close() try: os.environ['EMCC_DEBUG'] = '1' os.environ['EMCC_CORES'] = '2' # standardize over machines - for asm, linkable, chunks, js_chunks in [ - (0, 0, 2, 2), (0, 1, 2, 4), - (1, 0, 2, 2), (1, 1, 2, 4) + for asm, linkable, chunks in [ + (0, 0, 2), (0, 1, 2), + (1, 0, 2), (1, 1, 2) ]: - print asm, linkable, chunks, js_chunks + print asm, linkable, chunks output, err = Popen([PYTHON, EMCC, path_from_root('tests', 'hello_libcxx.cpp'), '-O1', '-s', 'LINKABLE=%d' % linkable, '-s', 'ASM_JS=%d' % asm] + (['-O2'] if asm else []), stdout=PIPE, stderr=PIPE).communicate() ok = False for c in range(chunks, chunks+2): ok = ok or ('phase 2 working on %d chunks' % c in err) assert ok, err - ok = False - for c in range(js_chunks, js_chunks+2): - ok = ok or ('splitting up js optimization into %d chunks' % c in err) - assert ok, err finally: del os.environ['EMCC_DEBUG'] del os.environ['EMCC_CORES'] @@ -1910,3 +1906,53 @@ done. assert '''tests/hello_world.c"''' in out assert '''printf("hello, world!''' in out + def test_demangle(self): + open('src.cpp', 'w').write(''' + #include <stdio.h> + #include <emscripten.h> + void two(char c) { + EM_ASM(Module.print(stackTrace())); + } + void one(int x) { + two(x % 17); + } + int main() { + EM_ASM(Module.print(demangle('__Znwj'))); // check for no aborts + EM_ASM(Module.print(demangle('_main'))); + EM_ASM(Module.print(demangle('__Z2f2v'))); + EM_ASM(Module.print(demangle('__Z12abcdabcdabcdi'))); + EM_ASM(Module.print(demangle('__Z4testcsifdPvPiPc'))); + EM_ASM(Module.print(demangle('__ZN4test5moarrEcslfdPvPiPc'))); + EM_ASM(Module.print(demangle('__ZN4Waka1f12a234123412345pointEv'))); + EM_ASM(Module.print(demangle('__Z3FooIiEvv'))); + EM_ASM(Module.print(demangle('__Z3FooIidEvi'))); + EM_ASM(Module.print(demangle('__ZN3Foo3BarILi5EEEvv'))); + EM_ASM(Module.print(demangle('__ZNK10__cxxabiv120__si_class_type_info16search_below_dstEPNS_19__dynamic_cast_infoEPKvib'))); + EM_ASM(Module.print(demangle('__Z9parsewordRPKciRi'))); + EM_ASM(Module.print(demangle('__Z5multiwahtjmxyz'))); + EM_ASM(Module.print(demangle('__Z1aA32_iPA5_c'))); + one(17); + return 0; + } + ''') + + Popen([PYTHON, EMCC, 'src.cpp', '-s', 'LINKABLE=1']).communicate() + output = run_js('a.out.js') + self.assertContained('''main +f2() +abcdabcdabcd(int) +test(char, short, int, float, double, void*, int*, char*) +test::moarr(char, short, long, float, double, void*, int*, char*) +Waka::f::a23412341234::point() +void Foo<int>() +void Foo<int, double>(int) +void Foo::Bar<5>() +__cxxabiv1::__si_class_type_info::search_below_dst(__cxxabiv1::__dynamic_cast_info*, void*, int, bool) +parseword(char*&, int, int&) +multi(wchar_t, signed char, unsigned char, unsigned short, unsigned int, unsigned long, long long, unsigned long long, ...) +a(int [32], char [5]*) +''', output) + # test for multiple functions in one stack trace + assert 'one(int)' in output + assert 'two(char)' in output + diff --git a/tests/test_sanity.py b/tests/test_sanity.py index aa3f1242..a0fff252 100644 --- a/tests/test_sanity.py +++ b/tests/test_sanity.py @@ -429,12 +429,12 @@ fi stdout=PIPE, stderr=PIPE).communicate() self.assertContained('hello, world!', run_js('a.out.js')) output = '\n'.join(output) - assert ('bootstrapping relooper succeeded' in output) == (i == 2), 'only bootstrap on first O2: ' + output - assert os.path.exists(RELOOPER) == (i >= 2), 'have relooper on O2: ' + output + assert ('bootstrapping relooper succeeded' in output) == (i == 1), 'only bootstrap on first O2: ' + output + assert os.path.exists(RELOOPER) == (i >= 1), 'have relooper on O2: ' + output src = open('a.out.js').read() main = src.split('function _main()')[1].split('\n}\n')[0] - assert ('while (1) {' in main or 'while(1){' in main or '} while ($' in main or '}while($' in main) == (i >= 2), 'reloop code on O2: ' + main - assert ('switch' not in main) == (i >= 2), 'reloop code on O2: ' + main + assert ('while (1) {' in main or 'while(1){' in main or 'while(1) {' in main or '} while ($' in main or '}while($' in main) == (i >= 1), 'reloop code on O2: ' + main + assert ('switch' not in main) == (i >= 1), 'reloop code on O2: ' + main def test_jcache(self): PRE_LOAD_MSG = 'loading pre from jcache' diff --git a/tools/find_bigfuncs.py b/tools/find_bigfuncs.py index c8e29833..79136343 100644 --- a/tools/find_bigfuncs.py +++ b/tools/find_bigfuncs.py @@ -18,6 +18,6 @@ for line in open(filename): size = i - start data.append([curr, size]) curr = None -#data.sort(lambda x, y: x[1] - y[1]) +data.sort(lambda x, y: x[1] - y[1]) print ''.join(['%6d : %s' % (x[1], x[0]) for x in data]) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index e5e0d287..022bdf47 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -3788,11 +3788,27 @@ function asmLastOpts(ast) { node[1] = simplifyNotCompsDirect(['unary-prefix', '!', conditionToBreak]); return node; } - } else if (type == 'binary' && node[1] == '&' && node[3][0] == 'unary-prefix' && node[3][1] == '-' && node[3][2][0] == 'num' && node[3][2][1] == 1) { - // Change &-1 into |0, at this point the hint is no longer needed - node[1] = '|'; - node[3] = node[3][2]; - node[3][1] = 0; + } else if (type == 'binary') { + if (node[1] === '&') { + if (node[3][0] === 'unary-prefix' && node[3][1] === '-' && node[3][2][0] === 'num' && node[3][2][1] === 1) { + // Change &-1 into |0, at this point the hint is no longer needed + node[1] = '|'; + node[3] = node[3][2]; + node[3][1] = 0; + } + } else if (node[1] === '-' && node[3][0] === 'unary-prefix') { + // avoid X - (-Y) because some minifiers buggily emit X--Y which is invalid as -- can be a unary. Transform to + // X + Y + if (node[3][1] === '-') { // integer + node[1] = '+'; + node[3] = node[3][2]; + } else if (node[3][1] === '+') { // float + if (node[3][2][0] === 'unary-prefix' && node[3][2][1] === '-') { + node[1] = '+'; + node[3][2] = node[3][2][2]; + } + } + } } }); }); diff --git a/tools/shared.py b/tools/shared.py index b3c3257b..c816f091 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -758,7 +758,6 @@ class Settings2(type): self.attrs['ASM_JS'] = 1 self.attrs['ASSERTIONS'] = 0 self.attrs['DISABLE_EXCEPTION_CATCHING'] = 1 - if opt_level >= 2: self.attrs['RELOOP'] = 1 self.attrs['ALIASING_FUNCTION_POINTERS'] = 1 if opt_level >= 3: @@ -1425,11 +1424,11 @@ class Building: emcc_leave_inputs_raw = os.environ.get('EMCC_LEAVE_INPUTS_RAW') if emcc_leave_inputs_raw: del os.environ['EMCC_LEAVE_INPUTS_RAW'] - def make(opt_level): + def make(opt_level, reloop): raw = relooper + '.raw.js' Building.emcc(os.path.join('relooper', 'Relooper.cpp'), ['-I' + os.path.join('relooper'), '--post-js', os.path.join('relooper', 'emscripten', 'glue.js'), - '--memory-init-file', '0', + '--memory-init-file', '0', '-s', 'RELOOP=%d' % reloop, '-s', 'EXPORTED_FUNCTIONS=["_rl_set_output_buffer","_rl_make_output_buffer","_rl_new_block","_rl_delete_block","_rl_block_add_branch_to","_rl_new_relooper","_rl_delete_relooper","_rl_relooper_add_block","_rl_relooper_calculate","_rl_relooper_render", "_rl_set_asm_js_mode"]', '-s', 'DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=["memcpy", "memset", "malloc", "free", "puts"]', '-s', 'RELOOPER="' + relooper + '"', @@ -1444,10 +1443,10 @@ class Building: # bootstrap phase 1: generate unrelooped relooper, for which we do not need a relooper (so we cannot recurse infinitely in this function) logging.info(' bootstrap phase 1') - make(1) + make(2, 0) # bootstrap phase 2: generate relooped relooper, using the unrelooped relooper (we see relooper.js exists so we cannot recurse infinitely in this function) logging.info(' bootstrap phase 2') - make(2) + make(2, 1) logging.info('bootstrapping relooper succeeded') logging.info('=======================================') ok = True diff --git a/tools/test-js-optimizer-asm-minlast-output.js b/tools/test-js-optimizer-asm-minlast-output.js new file mode 100644 index 00000000..d25c6d9b --- /dev/null +++ b/tools/test-js-optimizer-asm-minlast-output.js @@ -0,0 +1,2 @@ +function test($34){var $35=0;$35=$34+130.0;$35=$34+130;return $35|0} + diff --git a/tools/test-js-optimizer-asm-minlast.js b/tools/test-js-optimizer-asm-minlast.js new file mode 100644 index 00000000..6d172899 --- /dev/null +++ b/tools/test-js-optimizer-asm-minlast.js @@ -0,0 +1,8 @@ +function test($34) { + var $35 = 0; + $35=($34)-((+-130)); + $35=($34)-(-130); + return $35 | 0; +} +// EMSCRIPTEN_GENERATED_FUNCTIONS: ["test"] + |