diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/analyzer.js | 22 | ||||
-rw-r--r-- | src/compiler.js | 10 | ||||
-rw-r--r-- | src/experimental/allow_loopvars_from_memsetcpy_inasm.diff | 97 | ||||
-rw-r--r-- | src/experimental/simplifyGeneratedFunctionsDetection.diff | 336 | ||||
-rw-r--r-- | src/headless.js | 5 | ||||
-rw-r--r-- | src/intertyper.js | 23 | ||||
-rw-r--r-- | src/jsifier.js | 72 | ||||
-rw-r--r-- | src/library.js | 501 | ||||
-rw-r--r-- | src/library_browser.js | 80 | ||||
-rw-r--r-- | src/library_gl.js | 228 | ||||
-rw-r--r-- | src/library_openal.js | 471 | ||||
-rw-r--r-- | src/library_sdl.js | 105 | ||||
-rw-r--r-- | src/long.js | 18 | ||||
-rw-r--r-- | src/modules.js | 7 | ||||
-rw-r--r-- | src/parseTools.js | 86 | ||||
-rw-r--r-- | src/preamble.js | 34 | ||||
-rw-r--r-- | src/relooper/Relooper.cpp | 72 | ||||
-rw-r--r-- | src/relooper/fuzzer.py | 4 | ||||
-rw-r--r-- | src/relooper/test.txt | 4 | ||||
-rw-r--r-- | src/runtime.js | 66 | ||||
-rw-r--r-- | src/settings.js | 29 | ||||
-rw-r--r-- | src/shell.html | 9 | ||||
-rw-r--r-- | src/utility.js | 9 |
23 files changed, 1730 insertions, 558 deletions
diff --git a/src/analyzer.js b/src/analyzer.js index ecb5ea6b..92b7d8cf 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -18,7 +18,7 @@ function recomputeLines(func) { // Handy sets var BRANCH_INVOKE = set('branch', 'invoke'); -var LABEL_ENDERS = set('branch', 'return'); +var LABEL_ENDERS = set('branch', 'return', 'switch'); var SIDE_EFFECT_CAUSERS = set('call', 'invoke', 'atomic'); var UNUNFOLDABLE = set('value', 'structvalue', 'type', 'phiparam'); @@ -231,9 +231,10 @@ function analyzer(data, sidePass) { } if (isIllegalType(item.valueType) || isIllegalType(item.type)) { isIllegal = true; - } - if ((item.intertype == 'load' || item.intertype == 'store') && isStructType(item.valueType)) { + } else if ((item.intertype == 'load' || item.intertype == 'store') && isStructType(item.valueType)) { isIllegal = true; // storing an entire structure is illegal + } else if (item.intertype == 'mathop' && item.op == 'trunc' && isIllegalType(item.params[1].ident)) { // trunc stores target value in second ident + isIllegal = true; } }); if (!isIllegal) { @@ -290,7 +291,7 @@ function analyzer(data, sidePass) { var elements = getLegalParams([item.value], bits)[0]; var j = 0; elements.forEach(function(element) { - var tempVar = '$st$' + i + '$' + j; + var tempVar = '$st$' + (tempId++) + '$' + j; toAdd.push({ intertype: 'getelementptr', assignTo: tempVar, @@ -401,7 +402,7 @@ function analyzer(data, sidePass) { var j = 0; var toAdd = []; elements.forEach(function(element) { - var tempVar = '$st$' + i + '$' + j; + var tempVar = '$ld$' + (tempId++) + '$' + j; toAdd.push({ intertype: 'getelementptr', assignTo: tempVar, @@ -652,13 +653,14 @@ function analyzer(data, sidePass) { if (!isNumber(shifts)) { // We can't statically legalize this, do the operation at runtime TODO: optimize assert(sourceBits == 64, 'TODO: handle nonconstant shifts on != 64 bits'); + assert(PRECISE_I64_MATH, 'Must have precise i64 math for non-constant 64-bit shifts'); + Types.preciseI64MathUsed = 1; value.intertype = 'value'; - value.ident = 'Runtime' + (ASM_JS ? '_' : '.') + 'bitshift64(' + + value.ident = 'var ' + value.assignTo + '$0 = _bitshift64' + value.op[0].toUpperCase() + value.op.substr(1) + '(' + asmCoercion(sourceElements[0].ident, 'i32') + ',' + asmCoercion(sourceElements[1].ident, 'i32') + ',' + - Runtime['BITSHIFT64_' + value.op.toUpperCase()] + ',' + asmCoercion(value.params[1].ident + '$0', 'i32') + ');' + - 'var ' + value.assignTo + '$0 = ' + makeGetTempDouble(0, 'i32') + ', ' + value.assignTo + '$1 = ' + makeGetTempDouble(1, 'i32') + ';'; + 'var ' + value.assignTo + '$1 = tempRet0;'; value.assignTo = null; i++; continue; @@ -952,6 +954,7 @@ function analyzer(data, sidePass) { // Function parameters func.params.forEach(function(param) { if (param.intertype !== 'varargs') { + if (func.variables[param.ident]) warn('cannot have duplicate variable names: ' + param.ident); // toNiceIdent collisions? func.variables[param.ident] = { ident: param.ident, type: param.type, @@ -965,6 +968,7 @@ function analyzer(data, sidePass) { // Normal variables func.lines.forEach(function(item, i) { if (item.assignTo) { + if (func.variables[item.assignTo]) warn('cannot have duplicate variable names: ' + item.assignTo); // toNiceIdent collisions? var variable = func.variables[item.assignTo] = { ident: item.assignTo, type: item.type, @@ -1380,7 +1384,7 @@ function analyzer(data, sidePass) { var label = func.labels[i]; for (var j = 0; j < label.lines.length; j++) { var line = label.lines[j]; - if (line.intertype == 'call' && line.ident == setjmp) { + if ((line.intertype == 'call' || line.intertype == 'invoke') && line.ident == setjmp) { // Add a new label var oldIdent = label.ident; var newIdent = func.labelIdCounter++; diff --git a/src/compiler.js b/src/compiler.js index 3047daf1..bb72c7dd 100644 --- a/src/compiler.js +++ b/src/compiler.js @@ -141,8 +141,13 @@ if (phase == 'pre') { if (settings_file) { var settings = JSON.parse(read(settings_file)); - for (setting in settings) { - eval(setting + ' = ' + JSON.stringify(settings[setting])); + for (key in settings) { + var value = settings[key]; + if (value[0] == '@') { + // response file type thing, workaround for large inputs: value is @path-to-file + value = JSON.parse(read(value.substr(1))); + } + eval(key + ' = ' + JSON.stringify(value)); } } @@ -163,6 +168,7 @@ if (SAFE_HEAP >= 2) { EXPORTED_FUNCTIONS = set(EXPORTED_FUNCTIONS); EXPORTED_GLOBALS = set(EXPORTED_GLOBALS); EXCEPTION_CATCHING_WHITELIST = set(EXCEPTION_CATCHING_WHITELIST); +DEAD_FUNCTIONS = numberedSet(DEAD_FUNCTIONS); RUNTIME_DEBUG = LIBRARY_DEBUG || GL_DEBUG; diff --git a/src/experimental/allow_loopvars_from_memsetcpy_inasm.diff b/src/experimental/allow_loopvars_from_memsetcpy_inasm.diff new file mode 100644 index 00000000..a2dde7da --- /dev/null +++ b/src/experimental/allow_loopvars_from_memsetcpy_inasm.diff @@ -0,0 +1,97 @@ +commit a61ef3dbbaf7333ad67fca29c0aad5bcc99b653a +Author: Alon Zakai <alonzakai@gmail.com> +Date: Wed Mar 6 18:18:03 2013 -0800 + + handle new vars in asm code, such as the loop vars from memset/memcpy loops + +diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js +index f2dc516..65059e8 100644 +--- a/tools/js-optimizer.js ++++ b/tools/js-optimizer.js +@@ -1321,7 +1321,7 @@ function normalizeAsm(func) { + var name = v[0]; + var value = v[1]; + if (!(name in data.vars)) { +- assert(value[0] == 'num' || (value[0] == 'unary-prefix' && value[2][0] == 'num')); // must be valid coercion no-op ++ if (!(value[0] == 'num' || (value[0] == 'unary-prefix' && value[2][0] == 'num'))) break outer; // must be valid coercion no-op + data.vars[name] = detectAsmCoercion(value); + v.length = 1; // make an un-assigning var + } else { +@@ -1331,6 +1331,7 @@ function normalizeAsm(func) { + i++; + } + // finally, look for other var definitions and collect them ++ var extra = []; + while (i < stats.length) { + traverse(stats[i], function(node, type) { + if (type == 'var') { +@@ -1340,6 +1341,7 @@ function normalizeAsm(func) { + var value = v[1]; + if (!(name in data.vars)) { + data.vars[name] = detectAsmCoercion(value); ++ extra.push(['var', [[name]]]); // add a 'var' for normal JS + } + } + unVarify(node[1], node); +@@ -1353,6 +1355,7 @@ function normalizeAsm(func) { + }); + i++; + } ++ if (extra.length > 0) stats.splice.apply(stats, [0, 0].concat(extra)); + //printErr('normalized \n\n' + astToSrc(func) + '\n\nwith: ' + JSON.stringify(data)); + return data; + } +diff --git a/tools/test-js-optimizer-asm-regs-output.js b/tools/test-js-optimizer-asm-regs-output.js +index 99bccd2..f84b8d5 100644 +--- a/tools/test-js-optimizer-asm-regs-output.js ++++ b/tools/test-js-optimizer-asm-regs-output.js +@@ -9,6 +9,18 @@ function asm(d1, i2) { + d4 = d1 * 5; + return d4; + } ++function asm2(d1, i2) { ++ d1 = +d1; ++ i2 = i2 | 0; ++ var i3 = 0, d4 = +0; ++ i3 = i2; ++ i2 = d1 + i3 | 0; ++ d1 = d(Math_max(10, Math_min(5, f()))); ++ i3 = i2 + 2 | 0; ++ print(i3); ++ d4 = d1 * 5; ++ return d4; ++} + function _doit(i1, i2, i3) { + i1 = i1 | 0; + i2 = i2 | 0; +diff --git a/tools/test-js-optimizer-asm-regs.js b/tools/test-js-optimizer-asm-regs.js +index 0afced2..fbaa7c4 100644 +--- a/tools/test-js-optimizer-asm-regs.js ++++ b/tools/test-js-optimizer-asm-regs.js +@@ -10,6 +10,19 @@ function asm(x, y) { + double2 = double1*5; + return double2; + } ++function asm2(x, y) { ++ x = +x; ++ y = y | 0; ++ var int1 = 0, int2 = 0; // do not mix the types! ++ var double1 = +0, double2 = +0; ++ var tempy = y; ++ int1 = (x+tempy)|0; ++ double1 = d(Math.max(10, Math_min(5, f()))); ++ int2 = (int1+2)|0; ++ print(int2); ++ double2 = double1*5; ++ return double2; ++} + function _doit($x, $y$0, $y$1) { + $x = $x | 0; + $y$0 = $y$0 | 0; +@@ -41,5 +54,5 @@ function retf() { + } + // missing final return, need it as a float + } +-// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "_doit", "rett", "ret2t", "retf"] ++// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "asm2", "_doit", "rett", "ret2t", "retf"] + diff --git a/src/experimental/simplifyGeneratedFunctionsDetection.diff b/src/experimental/simplifyGeneratedFunctionsDetection.diff new file mode 100644 index 00000000..09e0ebcd --- /dev/null +++ b/src/experimental/simplifyGeneratedFunctionsDetection.diff @@ -0,0 +1,336 @@ +diff --git a/emscripten.py b/emscripten.py +index b698654..a7843c7 100755 +--- a/emscripten.py ++++ b/emscripten.py +@@ -294,8 +294,6 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, + outfile.write(blockaddrsize(indexize(pre))) + pre = None + +- #if DEBUG: outfile.write('// funcs\n') +- + # forward + forwarded_data = json.dumps(forwarded_json) + forwarded_file = temp_files.get('.2.json').name +@@ -483,9 +481,14 @@ Runtime.stackRestore = function(top) { asm.stackRestore(top) }; + else: + function_tables_defs = '\n'.join([table for table in last_forwarded_json['Functions']['tables'].itervalues()]) + outfile.write(function_tables_defs) ++ ++ outfile.write('// EMSCRIPTEN_START_FUNCS\n') ++ + outfile.write(blockaddrsize(indexize(funcs_js))) + funcs_js = None + ++ outfile.write('// EMSCRIPTEN_END_FUNCS\n') ++ + outfile.write(indexize(post)) + if DEBUG: print >> sys.stderr, ' emscript: phase 3 took %s seconds' % (time.time() - t) + +diff --git a/src/jsifier.js b/src/jsifier.js +index ff58ece..d6aa110 100644 +--- a/src/jsifier.js ++++ b/src/jsifier.js +@@ -1587,12 +1587,6 @@ function JSify(data, functionsOnly, givenFunctions) { + + var shellParts = read(shellFile).split('{{BODY}}'); + print(shellParts[1]); +- // Print out some useful metadata (for additional optimizations later, like the eliminator) +- if (EMIT_GENERATED_FUNCTIONS) { +- print('// EMSCRIPTEN_GENERATED_FUNCTIONS: ' + JSON.stringify(keys(Functions.implementedFunctions).filter(function(func) { +- return IGNORED_FUNCTIONS.indexOf(func.ident) < 0; +- })) + '\n'); +- } + + PassManager.serialize(); + +diff --git a/src/settings.js b/src/settings.js +index 1bfcf92..99c83f4 100644 +--- a/src/settings.js ++++ b/src/settings.js +@@ -336,8 +336,6 @@ var EXPLICIT_ZEXT = 0; // If 1, generate an explicit conversion of zext i1 to i3 + + var NECESSARY_BLOCKADDRS = []; // List of (function, block) for all block addresses that are taken. + +-var EMIT_GENERATED_FUNCTIONS = 0; // whether to emit the list of generated functions, needed for external JS optimization passes +- + // Compiler debugging options + var DEBUG_TAGS_SHOWING = []; + // Some useful items: +diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js +index f2dc516..9fa038c 100644 +--- a/tools/js-optimizer.js ++++ b/tools/js-optimizer.js +@@ -140,16 +140,6 @@ var UNDEFINED_NODE = ['unary-prefix', 'void', ['num', 0]]; + var TRUE_NODE = ['unary-prefix', '!', ['num', 0]]; + var FALSE_NODE = ['unary-prefix', '!', ['num', 1]]; + +-var GENERATED_FUNCTIONS_MARKER = '// EMSCRIPTEN_GENERATED_FUNCTIONS:'; +-var generatedFunctions = null; +-function setGeneratedFunctions(metadata) { +- var start = metadata.indexOf(GENERATED_FUNCTIONS_MARKER); +- generatedFunctions = set(eval(metadata.substr(start + GENERATED_FUNCTIONS_MARKER.length))); +-} +-function isGenerated(ident) { +- return ident in generatedFunctions; +-} +- + function srcToAst(src) { + return uglify.parser.parse(src); + } +@@ -212,21 +202,9 @@ function traverse(node, pre, post, stack) { + return result; + } + +-// Only walk through the generated functions +-function traverseGenerated(ast, pre, post, stack) { +- assert(generatedFunctions); +- traverse(ast, function(node) { +- if (node[0] == 'defun' && isGenerated(node[1])) { +- traverse(node, pre, post, stack); +- return null; +- } +- }); +-} +- +-function traverseGeneratedFunctions(ast, callback) { +- assert(generatedFunctions); ++function traverseFunctions(ast, callback) { + traverse(ast, function(node) { +- if (node[0] == 'defun' && isGenerated(node[1])) { ++ if (node[0] == 'defun') { + callback(node); + return null; + } +@@ -418,7 +396,7 @@ function simplifyExpressionsPre(ast) { + var rerun = true; + while (rerun) { + rerun = false; +- traverseGenerated(ast, function process(node, type, stack) { ++ traverse(ast, function process(node, type, stack) { + if (type == 'binary' && node[1] == '|') { + if (node[2][0] == 'num' && node[3][0] == 'num') { + return ['num', node[2][1] | node[3][1]]; +@@ -455,7 +433,7 @@ function simplifyExpressionsPre(ast) { + } + + // &-related optimizations +- traverseGenerated(ast, function(node, type) { ++ traverse(ast, function(node, type) { + if (type == 'binary' && node[1] == '&' && node[3][0] == 'num') { + if (node[2][0] == 'num') return ['num', node[2][1] & node[3][1]]; + var input = node[2]; +@@ -489,7 +467,7 @@ function simplifyExpressionsPre(ast) { + + if (asm) { + // optimize num >> num, in asm we need this here since we do not run optimizeShifts +- traverseGenerated(ast, function(node, type) { ++ traverse(ast, function(node, type) { + if (type == 'binary' && node[1] == '>>' && node[2][0] == 'num' && node[3][0] == 'num') { + node[0] = 'num'; + node[1] = node[2][1] >> node[3][1]; +@@ -505,7 +483,7 @@ function simplifyExpressionsPre(ast) { + var rerun = true; + while (rerun) { + rerun = false; +- traverseGenerated(ast, function(node, type) { ++ traverse(ast, function(node, type) { + if (type == 'binary' && node[1] == '+') { + if (node[2][0] == 'num' && node[3][0] == 'num') { + rerun = true; +@@ -528,7 +506,7 @@ function simplifyExpressionsPre(ast) { + + // if (x == 0) can be if (!x), etc. + function simplifyZeroComp(ast) { +- traverseGenerated(ast, function(node, type) { ++ traverse(ast, function(node, type) { + var binary; + if (type == 'if' && (binary = node[1])[0] == 'binary') { + if ((binary[1] == '!=' || binary[1] == '!==') && binary[3][0] == 'num' && binary[3][1] == 0) { +@@ -554,7 +532,7 @@ function simplifyExpressionsPre(ast) { + // TODO: when shifting a variable, if there are other uses, keep an unshifted version too, to prevent slowdowns? + function optimizeShiftsInternal(ast, conservative) { + var MAX_SHIFTS = 3; +- traverseGeneratedFunctions(ast, function(fun) { ++ traverseFunctions(ast, function(fun) { + var funMore = true; + var funFinished = {}; + while (funMore) { +@@ -999,7 +977,7 @@ function vacuum(ast) { + } break; + } + } +- traverseGeneratedFunctions(ast, function(node) { ++ traverseFunctions(ast, function(node) { + vacuumInternal(node); + simplifyNotComps(node); + }); +@@ -1021,7 +999,7 @@ function getStatements(node) { + // if (condition) { label == x } else .. + // We can hoist the multiple block into the condition, thus removing code and one 'if' check + function hoistMultiples(ast) { +- traverseGeneratedFunctions(ast, function(node) { ++ traverseFunctions(ast, function(node) { + traverse(node, function(node, type) { + var statements = getStatements(node); + if (!statements) return; +@@ -1135,7 +1113,7 @@ function hoistMultiples(ast) { + // if (..) { .. break|continue } else { .. } + // to + // if (..) { .. break|continue } .. +- traverseGenerated(ast, function(container, type) { ++ traverse(ast, function(container, type) { + var statements = getStatements(container); + if (!statements) return; + for (var i = 0; i < statements.length; i++) { +@@ -1168,7 +1146,7 @@ function loopOptimizer(ast) { + function passTwo(ast) { + var neededDos = []; + // Find unneeded labels +- traverseGenerated(ast, function(node, type, stack) { ++ traverse(ast, function(node, type, stack) { + if (type == 'label' && node[2][0] in LOOP) { + // this is a labelled loop. we don't know if it's needed yet. Mark its label for removal for now now. + stack.push(node); +@@ -1212,7 +1190,7 @@ function loopOptimizer(ast) { + // We return whether another pass is necessary + var more = false; + // Remove unneeded labels +- traverseGenerated(ast, function(node, type) { ++ traverse(ast, function(node, type) { + if (type == 'label' && node[1][0] == '+') { + more = true; + var ident = node[1].substr(1); +@@ -1227,13 +1205,13 @@ function loopOptimizer(ast) { + }); + // Remove unneeded one-time loops. We need such loops if (1) they have a label, or (2) they have a direct break so they are in neededDos. + // First, add all labeled loops of this nature to neededDos +- traverseGenerated(ast, function(node, type) { ++ traverse(ast, function(node, type) { + if (type == 'label' && node[2][0] == 'do') { + neededDos.push(node[2]); + } + }); + // Remove unneeded dos, we know who they are now +- traverseGenerated(ast, function(node, type) { ++ traverse(ast, function(node, type) { + if (type == 'do' && neededDos.indexOf(node) < 0) { + assert(jsonCompare(node[1], ['num', 0]), 'Trying to remove a one-time do loop that is not one of our generated ones.;'); + more = true; +@@ -1407,7 +1385,7 @@ function denormalizeAsm(func, data) { + // we still need the eliminator? Closure? And in what order? Perhaps just + // closure simple? + function registerize(ast) { +- traverseGeneratedFunctions(ast, function(fun) { ++ traverseFunctions(ast, function(fun) { + if (asm) var asmData = normalizeAsm(fun); + // Add parameters as a first (fake) var (with assignment), so they get taken into consideration + var params = {}; // note: params are special, they can never share a register between them (see later) +@@ -1671,7 +1649,7 @@ var ABORTING_ELIMINATOR_SCAN_NODES = set('new', 'object', 'function', 'defun', ' + + function eliminate(ast, memSafe) { + // Find variables that have a single use, and if they can be eliminated, do so +- traverseGeneratedFunctions(ast, function(func, type) { ++ traverseFunctions(ast, function(func, type) { + if (asm) var asmData = normalizeAsm(func); + //printErr('eliminate in ' + func[1]); + +@@ -2218,9 +2196,6 @@ var passes = { + var src = read(arguments_[0]); + var ast = srcToAst(src); + //printErr(JSON.stringify(ast)); throw 1; +-var metadata = src.split('\n').filter(function(line) { return line.indexOf(GENERATED_FUNCTIONS_MARKER) >= 0 })[0]; +-//assert(metadata, 'Must have EMSCRIPTEN_GENERATED_FUNCTIONS metadata'); +-if (metadata) setGeneratedFunctions(metadata); + + arguments_.slice(1).forEach(function(arg) { + passes[arg](ast); +diff --git a/tools/js_optimizer.py b/tools/js_optimizer.py +index 2fd2211..a253afb 100644 +--- a/tools/js_optimizer.py ++++ b/tools/js_optimizer.py +@@ -41,17 +41,17 @@ def run_on_js(filename, passes, js_engine, jcache): + if os.linesep != '\n': + js = js.replace(os.linesep, '\n') # we assume \n in the splitting code + +- # Find suffix +- suffix_marker = '// EMSCRIPTEN_GENERATED_FUNCTIONS' +- suffix_start = js.find(suffix_marker) +- suffix = '' +- if suffix_start >= 0: +- suffix = js[suffix_start:js.find('\n', suffix_start)] + '\n' +- # if there is metadata, we will run only on the generated functions. If there isn't, we will run on everything. +- generated = set(eval(suffix[len(suffix_marker)+1:])) +- +- if not suffix and jcache: +- # JCache cannot be used without metadata, since it might reorder stuff, and that's dangerous since only generated can be reordered ++ # Find markers ++ start_marker = '// EMSCRIPTEN_START_FUNCS\n' ++ end_marker = '// EMSCRIPTEN_END_FUNCS\n' ++ start = js.find(start_marker) ++ end = js.find(end_marker) ++ assert (start >= 0) == (end >= 0), 'must have both markers or neither' ++ have_markers = start >= 0 ++ # if there are markers, we will run only on the generated functions. If there isn't, we will run on everything. ++ ++ if not have_markers and jcache: ++ # JCache cannot be used without markers, since it might reorder stuff, and that's dangerous since only generated can be reordered + # This means jcache does not work after closure compiler runs, for example. But you won't get much benefit from jcache with closure + # anyhow (since closure is likely the longest part of the build). + if DEBUG: print >>sys.stderr, 'js optimizer: no metadata, so disabling jcache' +@@ -59,27 +59,10 @@ def run_on_js(filename, passes, js_engine, jcache): + + # If we process only generated code, find that and save the rest on the side + func_sig = re.compile('( *)function (_[\w$]+)\(') +- if suffix: +- pos = 0 +- gen_start = 0 +- gen_end = 0 +- while 1: +- m = func_sig.search(js, pos) +- if not m: break +- pos = m.end() +- indent = m.group(1) +- ident = m.group(2) +- if ident in generated: +- if not gen_start: +- gen_start = m.start() +- assert gen_start +- gen_end = js.find('\n%s}\n' % indent, m.end()) + (3 + len(indent)) +- assert gen_end > gen_start +- pre = js[:gen_start] +- post = js[gen_end:] +- if 'last' in passes: +- post = post.replace(suffix, '') # no need to write out the metadata - nothing after us needs it +- js = js[gen_start:gen_end] ++ if have_markers: ++ pre = js[:start + len(start_marker)] # includes start marker ++ post = js[end:] # includes end marker ++ js = js[start + len(start_marker):end] + else: + pre = '' + post = '' +@@ -95,7 +78,7 @@ def run_on_js(filename, passes, js_engine, jcache): + if m: + ident = m.group(2) + else: +- if suffix: continue # ignore whitespace ++ if have_markers: continue # ignore whitespace + ident = 'anon_%d' % i + assert ident + funcs.append((ident, func)) +@@ -131,7 +114,6 @@ def run_on_js(filename, passes, js_engine, jcache): + temp_file = temp_files.get('.jsfunc_%d.js' % i).name + f = open(temp_file, 'w') + f.write(chunk) +- f.write(suffix) + f.close() + return temp_file + filenames = [write_chunk(chunks[i], i) for i in range(len(chunks))] +@@ -169,7 +151,6 @@ def run_on_js(filename, passes, js_engine, jcache): + f.write(cached); # TODO: preserve order + f.write('\n') + f.write(post); +- # No need to write suffix: if there was one, it is inside post which exists when suffix is there + f.write('\n') + f.close() + diff --git a/src/headless.js b/src/headless.js index 8e847d27..d81fb5a3 100644 --- a/src/headless.js +++ b/src/headless.js @@ -537,7 +537,7 @@ var document = { case /* GL_MAX_FRAGMENT_UNIFORM_VECTORS */ 0x8DFD: return 4096; case /* GL_MAX_VARYING_VECTORS */ 0x8DFC: return 32; case /* GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS */ 0x8B4D: return 32; - default: throw 'getParameter ' + pname; + default: console.log('getParameter ' + pname + '?'); return 0; } }, getSupportedExtensions: function() { @@ -686,6 +686,7 @@ var document = { document.callEventListeners('pointerlockchange'); }); }, + exitPointerLock: function(){}, style: {}, eventListeners: {}, addEventListener: document.addEventListener, @@ -748,6 +749,8 @@ var document = { body: { appendChild: function(){}, }, + exitPointerLock: function(){}, + cancelFullScreen: function(){}, }; var alert = function(x) { print(x); diff --git a/src/intertyper.js b/src/intertyper.js index 2103ecfa..57e3011d 100644 --- a/src/intertyper.js +++ b/src/intertyper.js @@ -122,16 +122,19 @@ function intertyper(data, sidePass, baseLineNums) { SKIP_STACK_IN_SMALL = 0; } - unparsedBundles.push({ - intertype: 'unparsedFunction', - // We need this early, to know basic function info - ident, params, varargs - ident: toNiceIdent(func.ident), - params: func.params, - returnType: func.returnType, - hasVarArgs: func.hasVarArgs, - lineNum: currFunctionLineNum, - lines: currFunctionLines - }); + var ident = toNiceIdent(func.ident); + if (!(ident in DEAD_FUNCTIONS)) { + unparsedBundles.push({ + intertype: 'unparsedFunction', + // We need this early, to know basic function info - ident, params, varargs + ident: ident, + params: func.params, + returnType: func.returnType, + hasVarArgs: func.hasVarArgs, + lineNum: currFunctionLineNum, + lines: currFunctionLines + }); + } currFunctionLines = []; } } diff --git a/src/jsifier.js b/src/jsifier.js index ff58ece2..4263618a 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -397,6 +397,20 @@ function JSify(data, functionsOnly, givenFunctions) { } }); + function processLibraryFunction(snippet, ident) { + snippet = snippet.toString(); + assert(snippet.indexOf('XXX missing C define') == -1, + 'Trying to include a library function with missing C defines: ' + ident + ' | ' + snippet); + + // name the function; overwrite if it's already named + snippet = snippet.replace(/function(?:\s+([^(]+))?\s*\(/, 'function _' + ident + '('); + if (LIBRARY_DEBUG) { + snippet = snippet.replace('{', '{ var ret = (function() { if (Runtime.debug) Module.printErr("[library call:' + ident + ': " + Array.prototype.slice.call(arguments).map(Runtime.prettyPrint) + "]"); '); + snippet = snippet.substr(0, snippet.length-1) + '}).apply(this, arguments); if (Runtime.debug && typeof ret !== "undefined") Module.printErr(" [ return:" + Runtime.prettyPrint(ret)); return ret; \n}'; + } + return snippet; + } + // functionStub substrate.addActor('FunctionStub', { processItem: function(item) { @@ -434,16 +448,7 @@ function JSify(data, functionsOnly, givenFunctions) { snippet = stringifyWithFunctions(snippet); } else if (typeof snippet === 'function') { isFunction = true; - snippet = snippet.toString(); - assert(snippet.indexOf('XXX missing C define') == -1, - 'Trying to include a library function with missing C defines: ' + ident + ' | ' + snippet); - - // name the function; overwrite if it's already named - snippet = snippet.replace(/function(?:\s+([^(]+))?\s*\(/, 'function _' + ident + '('); - if (LIBRARY_DEBUG) { - snippet = snippet.replace('{', '{ var ret = (function() { if (Runtime.debug) Module.printErr("[library call:' + ident + ': " + Array.prototype.slice.call(arguments).map(Runtime.prettyPrint) + "]"); '); - snippet = snippet.substr(0, snippet.length-1) + '}).apply(this, arguments); if (Runtime.debug && typeof ret !== "undefined") Module.printErr(" [ return:" + Runtime.prettyPrint(ret)); return ret; \n}'; - } + snippet = processLibraryFunction(snippet, ident); if (ASM_JS) Functions.libraryFunctions[ident] = 1; } @@ -600,6 +605,10 @@ function JSify(data, functionsOnly, givenFunctions) { func.JS += 'function ' + func.ident + '(' + paramIdents.join(', ') + ') {\n'; + if (PGO) { + func.JS += ' PGOMonitor.called["' + func.ident + '"] = 1;\n'; + } + if (ASM_JS) { // spell out argument types func.params.forEach(function(param) { @@ -731,7 +740,7 @@ function JSify(data, functionsOnly, givenFunctions) { return indent + ' case ' + getLabelId(label.ident) + ': ' + (SHOW_LABELS ? '// ' + getOriginalLabelId(label.ident) : '') + '\n' + getLabelLines(label, indent + ' '); }).join('\n') + '\n'; - if (ASSERTIONS) ret += indent + ' default: assert(0, "bad label: " + label);\n'; + if (ASSERTIONS) ret += indent + ' default: assert(0' + (ASM_JS ? '' : ', "bad label: " + label') + ');\n'; ret += indent + '}\n'; if (func.setjmpTable) { ret += ' } catch(e) { if (!e.longjmp || !(e.id in mySetjmpIds)) throw(e); setjmpTable[setjmpLabels[e.id]](e.value) }'; @@ -1312,6 +1321,8 @@ function JSify(data, functionsOnly, givenFunctions) { } else { callIdent = ident; } + if (callIdent == '0') return 'abort(-2)'; + var args = []; var argsTypes = []; var varargs = []; @@ -1414,6 +1425,12 @@ function JSify(data, functionsOnly, givenFunctions) { returnType = getReturnType(type); } + if (callIdent in DEAD_FUNCTIONS) { + var ret = 'abort(' + DEAD_FUNCTIONS[callIdent] + ')'; + if (ASM_JS) ret = asmCoercion(ret, returnType); + return ret; + } + if (byPointer) { var sig = Functions.getSignature(returnType, argsTypes, hasVarArgs); if (ASM_JS) { @@ -1443,7 +1460,7 @@ function JSify(data, functionsOnly, givenFunctions) { makeFuncLineActor('unreachable', function(item) { if (ASSERTIONS) { - return 'throw "Reached an unreachable!"'; + return ASM_JS ? 'abort()' : 'throw "Reached an unreachable!"'; } else { return ';'; } @@ -1552,16 +1569,25 @@ function JSify(data, functionsOnly, givenFunctions) { // This is the main 'post' pass. Print out the generated code that we have here, together with the // rest of the output that we started to print out earlier (see comment on the // "Final shape that will be created"). - if (CORRUPTION_CHECK) { - assert(!ASM_JS); // cannot monkeypatch asm! - print(processMacros(read('corruptionCheck.js'))); - } if (PRECISE_I64_MATH && Types.preciseI64MathUsed) { + if (!INCLUDE_FULL_LIBRARY) { + ['i64Add', 'bitshift64Shl', 'bitshift64Lshr', 'bitshift64Ashr'].forEach(function(func) { + print(processLibraryFunction(LibraryManager.library[func], func)); // must be first to be close to generated code + Functions.implementedFunctions['_' + func] = LibraryManager.library[func + '__sig']; + }); + } + print('// EMSCRIPTEN_END_FUNCS\n'); print(read('long.js')); } else { + print('// EMSCRIPTEN_END_FUNCS\n'); print('// Warning: printing of i64 values may be slightly rounded! No deep i64 math used, so precise i64 code not included'); print('var i64Math = null;'); } + + if (CORRUPTION_CHECK) { + assert(!ASM_JS); // cannot monkeypatch asm! + print(processMacros(read('corruptionCheck.js'))); + } if (HEADLESS) { print('if (!ENVIRONMENT_IS_WEB) {'); print(read('headless.js').replace("'%s'", "'http://emscripten.org'").replace("'?%s'", "''").replace('%s,', 'null,').replace('%d', '0')); @@ -1587,11 +1613,17 @@ function JSify(data, functionsOnly, givenFunctions) { var shellParts = read(shellFile).split('{{BODY}}'); print(shellParts[1]); - // Print out some useful metadata (for additional optimizations later, like the eliminator) - if (EMIT_GENERATED_FUNCTIONS) { - print('// EMSCRIPTEN_GENERATED_FUNCTIONS: ' + JSON.stringify(keys(Functions.implementedFunctions).filter(function(func) { + // Print out some useful metadata + if (EMIT_GENERATED_FUNCTIONS || PGO) { + var generatedFunctions = JSON.stringify(keys(Functions.implementedFunctions).filter(function(func) { return IGNORED_FUNCTIONS.indexOf(func.ident) < 0; - })) + '\n'); + })); + if (PGO) { + print('PGOMonitor.allGenerated = ' + generatedFunctions + ';\nremoveRunDependency("pgo");\n'); + } + if (EMIT_GENERATED_FUNCTIONS) { + print('// EMSCRIPTEN_GENERATED_FUNCTIONS: ' + generatedFunctions + '\n'); + } } PassManager.serialize(); diff --git a/src/library.js b/src/library.js index 1cd7e3f5..45187d8d 100644 --- a/src/library.js +++ b/src/library.js @@ -504,7 +504,7 @@ LibraryManager.library = { } var utf8 = new Runtime.UTF8Processor(); function simpleOutput(val) { - if (val === null || val === '\n'.charCodeAt(0)) { + if (val === null || val === {{{ charCode('\n') }}}) { output.printer(output.buffer.join('')); output.buffer = []; } else { @@ -600,8 +600,8 @@ LibraryManager.library = { quit: function() { if (!FS.init.initialized) return; // Flush any partially-printed lines in stdout and stderr. Careful, they may have been closed - if (FS.streams[2] && FS.streams[2].object.output.buffer.length > 0) FS.streams[2].object.output('\n'.charCodeAt(0)); - if (FS.streams[3] && FS.streams[3].object.output.buffer.length > 0) FS.streams[3].object.output('\n'.charCodeAt(0)); + if (FS.streams[2] && FS.streams[2].object.output.buffer.length > 0) FS.streams[2].object.output({{{ charCode('\n') }}}); + if (FS.streams[3] && FS.streams[3].object.output.buffer.length > 0) FS.streams[3].object.output({{{ charCode('\n') }}}); }, // Standardizes a path. Useful for making comparisons of pathnames work in a consistent manner. @@ -828,11 +828,11 @@ LibraryManager.library = { // Null or empty results in '.'. var me = ___libgenSplitName; if (!me.ret) { - me.ret = allocate(['.'.charCodeAt(0), 0], 'i8', ALLOC_NORMAL); + me.ret = allocate([{{{ charCode('.') }}}, 0], 'i8', ALLOC_NORMAL); } return [me.ret, -1]; } else { - var slash = '/'.charCodeAt(0); + var slash = {{{ charCode('/') }}}; var allSlashes = true; var slashPositions = []; for (var i = 0; {{{ makeGetValue('path', 'i', 'i8') }}} !== 0; i++) { @@ -1730,7 +1730,12 @@ LibraryManager.library = { } var contents = stream.object.contents; var size = Math.min(contents.length - offset, nbyte); - if (contents.subarray || contents.slice) { // typed array or normal array +#if USE_TYPED_ARRAYS == 2 + if (contents.subarray) { // typed array + HEAPU8.set(contents.subarray(offset, offset+size), buf); + } else +#endif + if (contents.slice) { // normal array for (var i = 0; i < size; i++) { {{{ makeSetValue('buf', 'i', 'contents[offset + i]', 'i8') }}} } @@ -2403,6 +2408,7 @@ LibraryManager.library = { case {{{ cDefine('_SC_STREAM_MAX') }}}: return 16; case {{{ cDefine('_SC_TZNAME_MAX') }}}: return 6; case {{{ cDefine('_SC_THREAD_DESTRUCTOR_ITERATIONS') }}}: return 4; + case {{{ cDefine('_SC_NPROCESSORS_ONLN') }}}: return 1; } ___setErrNo(ERRNO_CODES.EINVAL); return -1; @@ -2451,9 +2457,9 @@ LibraryManager.library = { _scanString: function(format, get, unget, varargs) { if (!__scanString.whiteSpace) { __scanString.whiteSpace = {}; - __scanString.whiteSpace[' '.charCodeAt(0)] = 1; - __scanString.whiteSpace['\t'.charCodeAt(0)] = 1; - __scanString.whiteSpace['\n'.charCodeAt(0)] = 1; + __scanString.whiteSpace[{{{ charCode(' ') }}}] = 1; + __scanString.whiteSpace[{{{ charCode('\t') }}}] = 1; + __scanString.whiteSpace[{{{ charCode('\n') }}}] = 1; __scanString.whiteSpace[' '] = 1; __scanString.whiteSpace['\t'] = 1; __scanString.whiteSpace['\n'] = 1; @@ -2513,8 +2519,8 @@ LibraryManager.library = { if (format[formatIndex] === '%') { formatIndex++; var maxSpecifierStart = formatIndex; - while (format[formatIndex].charCodeAt(0) >= '0'.charCodeAt(0) && - format[formatIndex].charCodeAt(0) <= '9'.charCodeAt(0)) { + while (format[formatIndex].charCodeAt(0) >= {{{ charCode('0') }}} && + format[formatIndex].charCodeAt(0) <= {{{ charCode('9') }}}) { formatIndex++; } var max_; @@ -2560,11 +2566,11 @@ LibraryManager.library = { while ((curr < max_ || isNaN(max_)) && next > 0) { if (!(next in __scanString.whiteSpace) && // stop on whitespace (type == 's' || - ((type === 'd' || type == 'u' || type == 'i') && ((next >= '0'.charCodeAt(0) && next <= '9'.charCodeAt(0)) || - (first && next == '-'.charCodeAt(0)))) || - (type === 'x' && (next >= '0'.charCodeAt(0) && next <= '9'.charCodeAt(0) || - next >= 'a'.charCodeAt(0) && next <= 'f'.charCodeAt(0) || - next >= 'A'.charCodeAt(0) && next <= 'F'.charCodeAt(0)))) && + ((type === 'd' || type == 'u' || type == 'i') && ((next >= {{{ charCode('0') }}} && next <= {{{ charCode('9') }}}) || + (first && next == {{{ charCode('-') }}}))) || + (type === 'x' && (next >= {{{ charCode('0') }}} && next <= {{{ charCode('9') }}} || + next >= {{{ charCode('a') }}} && next <= {{{ charCode('f') }}} || + next >= {{{ charCode('A') }}} && next <= {{{ charCode('F') }}}))) && (formatIndex >= format.length || next !== format[formatIndex].charCodeAt(0))) { // Stop when we read something that is coming up buffer.push(String.fromCharCode(next)); next = get(); @@ -2669,7 +2675,7 @@ LibraryManager.library = { curr = {{{ makeGetValue(0, 'textIndex', 'i8') }}}; if (curr === 0) break; next = {{{ makeGetValue(0, 'textIndex+1', 'i8') }}}; - if (curr == '%'.charCodeAt(0)) { + if (curr == {{{ charCode('%') }}}) { // Handle flags. var flagAlwaysSigned = false; var flagLeftAlign = false; @@ -2677,16 +2683,16 @@ LibraryManager.library = { var flagZeroPad = false; flagsLoop: while (1) { switch (next) { - case '+'.charCodeAt(0): + case {{{ charCode('+') }}}: flagAlwaysSigned = true; break; - case '-'.charCodeAt(0): + case {{{ charCode('-') }}}: flagLeftAlign = true; break; - case '#'.charCodeAt(0): + case {{{ charCode('#') }}}: flagAlternative = true; break; - case '0'.charCodeAt(0): + case {{{ charCode('0') }}}: if (flagZeroPad) { break flagsLoop; } else { @@ -2702,13 +2708,13 @@ LibraryManager.library = { // Handle width. var width = 0; - if (next == '*'.charCodeAt(0)) { + if (next == {{{ charCode('*') }}}) { width = getNextArg('i32'); textIndex++; next = {{{ makeGetValue(0, 'textIndex+1', 'i8') }}}; } else { - while (next >= '0'.charCodeAt(0) && next <= '9'.charCodeAt(0)) { - width = width * 10 + (next - '0'.charCodeAt(0)); + while (next >= {{{ charCode('0') }}} && next <= {{{ charCode('9') }}}) { + width = width * 10 + (next - {{{ charCode('0') }}}); textIndex++; next = {{{ makeGetValue(0, 'textIndex+1', 'i8') }}}; } @@ -2716,20 +2722,20 @@ LibraryManager.library = { // Handle precision. var precisionSet = false; - if (next == '.'.charCodeAt(0)) { + if (next == {{{ charCode('.') }}}) { var precision = 0; precisionSet = true; textIndex++; next = {{{ makeGetValue(0, 'textIndex+1', 'i8') }}}; - if (next == '*'.charCodeAt(0)) { + if (next == {{{ charCode('*') }}}) { precision = getNextArg('i32'); textIndex++; } else { while(1) { var precisionChr = {{{ makeGetValue(0, 'textIndex+1', 'i8') }}}; - if (precisionChr < '0'.charCodeAt(0) || - precisionChr > '9'.charCodeAt(0)) break; - precision = precision * 10 + (precisionChr - '0'.charCodeAt(0)); + if (precisionChr < {{{ charCode('0') }}} || + precisionChr > {{{ charCode('9') }}}) break; + precision = precision * 10 + (precisionChr - {{{ charCode('0') }}}); textIndex++; } } @@ -2743,7 +2749,7 @@ LibraryManager.library = { switch (String.fromCharCode(next)) { case 'h': var nextNext = {{{ makeGetValue(0, 'textIndex+2', 'i8') }}}; - if (nextNext == 'h'.charCodeAt(0)) { + if (nextNext == {{{ charCode('h') }}}) { textIndex++; argSize = 1; // char (actually i32 in varargs) } else { @@ -2752,7 +2758,7 @@ LibraryManager.library = { break; case 'l': var nextNext = {{{ makeGetValue(0, 'textIndex+2', 'i8') }}}; - if (nextNext == 'l'.charCodeAt(0)) { + if (nextNext == {{{ charCode('l') }}}) { textIndex++; argSize = 8; // long long } else { @@ -2778,7 +2784,7 @@ LibraryManager.library = { // Handle type specifier. if (['d', 'i', 'u', 'o', 'x', 'X', 'p'].indexOf(String.fromCharCode(next)) != -1) { // Integer. - var signed = next == 'd'.charCodeAt(0) || next == 'i'.charCodeAt(0); + var signed = next == {{{ charCode('d') }}} || next == {{{ charCode('i') }}}; argSize = argSize || 4; var currArg = getNextArg('i' + (argSize * 8)); #if PRECISE_I64_MATH @@ -2788,7 +2794,7 @@ LibraryManager.library = { #if USE_TYPED_ARRAYS == 2 // Flatten i64-1 [low, high] into a (slightly rounded) double if (argSize == 8) { - currArg = Runtime.makeBigInt(currArg[0], currArg[1], next == 'u'.charCodeAt(0)); + currArg = Runtime.makeBigInt(currArg[0], currArg[1], next == {{{ charCode('u') }}}); } #endif // Truncate to requested size. @@ -2799,20 +2805,20 @@ LibraryManager.library = { // Format the number. var currAbsArg = Math.abs(currArg); var prefix = ''; - if (next == 'd'.charCodeAt(0) || next == 'i'.charCodeAt(0)) { + if (next == {{{ charCode('d') }}} || next == {{{ charCode('i') }}}) { #if PRECISE_I64_MATH if (argSize == 8 && i64Math) argText = i64Math.stringify(origArg[0], origArg[1], null); else #endif argText = reSign(currArg, 8 * argSize, 1).toString(10); - } else if (next == 'u'.charCodeAt(0)) { + } else if (next == {{{ charCode('u') }}}) { #if PRECISE_I64_MATH if (argSize == 8 && i64Math) argText = i64Math.stringify(origArg[0], origArg[1], true); else #endif argText = unSign(currArg, 8 * argSize, 1).toString(10); currArg = Math.abs(currArg); - } else if (next == 'o'.charCodeAt(0)) { + } else if (next == {{{ charCode('o') }}}) { argText = (flagAlternative ? '0' : '') + currAbsArg.toString(8); - } else if (next == 'x'.charCodeAt(0) || next == 'X'.charCodeAt(0)) { + } else if (next == {{{ charCode('x') }}} || next == {{{ charCode('X') }}}) { prefix = flagAlternative ? '0x' : ''; #if PRECISE_I64_MATH if (argSize == 8 && i64Math) argText = (origArg[1]>>>0).toString(16) + (origArg[0]>>>0).toString(16); else @@ -2830,11 +2836,11 @@ LibraryManager.library = { } else { argText = currAbsArg.toString(16); } - if (next == 'X'.charCodeAt(0)) { + if (next == {{{ charCode('X') }}}) { prefix = prefix.toUpperCase(); argText = argText.toUpperCase(); } - } else if (next == 'p'.charCodeAt(0)) { + } else if (next == {{{ charCode('p') }}}) { if (currAbsArg === 0) { argText = '(nil)'; } else { @@ -2892,27 +2898,27 @@ LibraryManager.library = { // Convert g/G to f/F or e/E, as per: // http://pubs.opengroup.org/onlinepubs/9699919799/functions/printf.html - if (next == 'g'.charCodeAt(0) || next == 'G'.charCodeAt(0)) { + if (next == {{{ charCode('g') }}} || next == {{{ charCode('G') }}}) { isGeneral = true; precision = precision || 1; var exponent = parseInt(currArg.toExponential(effectivePrecision).split('e')[1], 10); if (precision > exponent && exponent >= -4) { - next = ((next == 'g'.charCodeAt(0)) ? 'f' : 'F').charCodeAt(0); + next = ((next == {{{ charCode('g') }}}) ? 'f' : 'F').charCodeAt(0); precision -= exponent + 1; } else { - next = ((next == 'g'.charCodeAt(0)) ? 'e' : 'E').charCodeAt(0); + next = ((next == {{{ charCode('g') }}}) ? 'e' : 'E').charCodeAt(0); precision--; } effectivePrecision = Math.min(precision, 20); } - if (next == 'e'.charCodeAt(0) || next == 'E'.charCodeAt(0)) { + if (next == {{{ charCode('e') }}} || next == {{{ charCode('E') }}}) { argText = currArg.toExponential(effectivePrecision); // Make sure the exponent has at least 2 digits. if (/[eE][-+]\d$/.test(argText)) { argText = argText.slice(0, -1) + '0' + argText.slice(-1); } - } else if (next == 'f'.charCodeAt(0) || next == 'F'.charCodeAt(0)) { + } else if (next == {{{ charCode('f') }}} || next == {{{ charCode('F') }}}) { argText = currArg.toFixed(effectivePrecision); } @@ -2932,7 +2938,7 @@ LibraryManager.library = { argText = parts[0] + (parts.length > 1 ? 'e' + parts[1] : ''); // Capitalize 'E' if needed. - if (next == 'E'.charCodeAt(0)) argText = argText.toUpperCase(); + if (next == {{{ charCode('E') }}}) argText = argText.toUpperCase(); // Add sign. if (flagAlwaysSigned && currArg >= 0) { @@ -2954,20 +2960,20 @@ LibraryManager.library = { } // Adjust case. - if (next < 'a'.charCodeAt(0)) argText = argText.toUpperCase(); + if (next < {{{ charCode('a') }}}) argText = argText.toUpperCase(); // Insert the result into the buffer. argText.split('').forEach(function(chr) { ret.push(chr.charCodeAt(0)); }); - } else if (next == 's'.charCodeAt(0)) { + } else if (next == {{{ charCode('s') }}}) { // String. var arg = getNextArg('i8*') || nullString; var argLength = _strlen(arg); if (precisionSet) argLength = Math.min(argLength, precision); if (!flagLeftAlign) { while (argLength < width--) { - ret.push(' '.charCodeAt(0)); + ret.push({{{ charCode(' ') }}}); } } for (var i = 0; i < argLength; i++) { @@ -2975,21 +2981,21 @@ LibraryManager.library = { } if (flagLeftAlign) { while (argLength < width--) { - ret.push(' '.charCodeAt(0)); + ret.push({{{ charCode(' ') }}}); } } - } else if (next == 'c'.charCodeAt(0)) { + } else if (next == {{{ charCode('c') }}}) { // Character. if (flagLeftAlign) ret.push(getNextArg('i8')); while (--width > 0) { - ret.push(' '.charCodeAt(0)); + ret.push({{{ charCode(' ') }}}); } if (!flagLeftAlign) ret.push(getNextArg('i8')); - } else if (next == 'n'.charCodeAt(0)) { + } else if (next == {{{ charCode('n') }}}) { // Write the length written so far to the next parameter. var ptr = getNextArg('i32*'); {{{ makeSetValue('ptr', '0', 'ret.length', 'i32') }}} - } else if (next == '%'.charCodeAt(0)) { + } else if (next == {{{ charCode('%') }}}) { // Literal percent sign. ret.push(curr); } else { @@ -3138,7 +3144,7 @@ LibraryManager.library = { var streamObj = FS.streams[stream]; if (streamObj.error || streamObj.eof) return 0; var byte_; - for (var i = 0; i < n - 1 && byte_ != '\n'.charCodeAt(0); i++) { + for (var i = 0; i < n - 1 && byte_ != {{{ charCode('\n') }}}; i++) { byte_ = _fgetc(stream); if (byte_ == -1) { if (streamObj.error) return 0; @@ -3244,7 +3250,7 @@ LibraryManager.library = { if (ret < 0) { return ret; } else { - var newlineRet = _fputc('\n'.charCodeAt(0), stdout); + var newlineRet = _fputc({{{ charCode('\n') }}}, stdout); return (newlineRet < 0) ? -1 : ret + 1; } }, @@ -3370,8 +3376,8 @@ LibraryManager.library = { var stdout = {{{ makeGetValue(makeGlobalUse('_stdout'), '0', 'void*') }}}; if (s) { _fputs(s, stdout); - _fputc(':'.charCodeAt(0), stdout); - _fputc(' '.charCodeAt(0), stdout); + _fputc({{{ charCode(':') }}}, stdout); + _fputc({{{ charCode(' ') }}}, stdout); } var errnum = {{{ makeGetValue('___errno_location()', '0', 'i32') }}}; _puts(_strerror(errnum)); @@ -3669,6 +3675,17 @@ LibraryManager.library = { abs: 'Math.abs', labs: 'Math.abs', +#if USE_TYPED_ARRAYS == 2 + llabs__deps: [function() { Types.preciseI64MathUsed = 1 }], + llabs: function(lo, hi) { + i64Math.abs(lo, hi); + {{{ makeStructuralReturn([makeGetTempDouble(0, 'i32'), makeGetTempDouble(1, 'i32')]) }}}; + }, +#else + llabs: function(lo, hi) { + throw 'unsupported llabs'; + }, +#endif exit__deps: ['_exit'], exit: function(status) { @@ -3726,93 +3743,6 @@ LibraryManager.library = { return ret; }, - strtod__deps: ['isspace', 'isdigit'], - strtod: function(str, endptr) { - var origin = str; - - // Skip space. - while (_isspace({{{ makeGetValue('str', 0, 'i8') }}})) str++; - - // Check for a plus/minus sign. - var multiplier = 1; - if ({{{ makeGetValue('str', 0, 'i8') }}} == '-'.charCodeAt(0)) { - multiplier = -1; - str++; - } else if ({{{ makeGetValue('str', 0, 'i8') }}} == '+'.charCodeAt(0)) { - str++; - } - - var chr; - var ret = 0; - - // Get whole part. - var whole = false; - while(1) { - chr = {{{ makeGetValue('str', 0, 'i8') }}}; - if (!_isdigit(chr)) break; - whole = true; - ret = ret*10 + chr - '0'.charCodeAt(0); - str++; - } - - // Get fractional part. - var fraction = false; - if ({{{ makeGetValue('str', 0, 'i8') }}} == '.'.charCodeAt(0)) { - str++; - var mul = 1/10; - while(1) { - chr = {{{ makeGetValue('str', 0, 'i8') }}}; - if (!_isdigit(chr)) break; - fraction = true; - ret += mul*(chr - '0'.charCodeAt(0)); - mul /= 10; - str++; - } - } - - if (!whole && !fraction) { - if (endptr) { - {{{ makeSetValue('endptr', 0, 'origin', '*') }}} - } - return 0; - } - - // Get exponent part. - chr = {{{ makeGetValue('str', 0, 'i8') }}}; - if (chr == 'e'.charCodeAt(0) || chr == 'E'.charCodeAt(0)) { - str++; - var exponent = 0; - var expNegative = false; - chr = {{{ makeGetValue('str', 0, 'i8') }}}; - if (chr == '-'.charCodeAt(0)) { - expNegative = true; - str++; - } else if (chr == '+'.charCodeAt(0)) { - str++; - } - chr = {{{ makeGetValue('str', 0, 'i8') }}}; - while(1) { - if (!_isdigit(chr)) break; - exponent = exponent*10 + chr - '0'.charCodeAt(0); - str++; - chr = {{{ makeGetValue('str', 0, 'i8') }}}; - } - if (expNegative) exponent = -exponent; - ret *= Math.pow(10, exponent); - } - - // Set end pointer. - if (endptr) { - {{{ makeSetValue('endptr', 0, 'str', '*') }}} - } - - return ret * multiplier; - }, - strtod_l: 'strtod', // no locale support yet - strtold: 'strtod', // XXX add real support for long double - strtold_l: 'strtold', // no locale support yet - strtof: 'strtod', // use stdtod to handle strtof - _parseInt__deps: ['isspace', '__setErrNo', '$ERRNO_CODES'], _parseInt: function(str, endptr, base, min, max, bits, unsign) { // Skip space. @@ -3820,19 +3750,19 @@ LibraryManager.library = { // Check for a plus/minus sign. var multiplier = 1; - if ({{{ makeGetValue('str', 0, 'i8') }}} == '-'.charCodeAt(0)) { + if ({{{ makeGetValue('str', 0, 'i8') }}} == {{{ charCode('-') }}}) { multiplier = -1; str++; - } else if ({{{ makeGetValue('str', 0, 'i8') }}} == '+'.charCodeAt(0)) { + } else if ({{{ makeGetValue('str', 0, 'i8') }}} == {{{ charCode('+') }}}) { str++; } // Find base. var finalBase = base; if (!finalBase) { - if ({{{ makeGetValue('str', 0, 'i8') }}} == '0'.charCodeAt(0)) { - if ({{{ makeGetValue('str+1', 0, 'i8') }}} == 'x'.charCodeAt(0) || - {{{ makeGetValue('str+1', 0, 'i8') }}} == 'X'.charCodeAt(0)) { + if ({{{ makeGetValue('str', 0, 'i8') }}} == {{{ charCode('0') }}}) { + if ({{{ makeGetValue('str+1', 0, 'i8') }}} == {{{ charCode('x') }}} || + {{{ makeGetValue('str+1', 0, 'i8') }}} == {{{ charCode('X') }}}) { finalBase = 16; str += 2; } else { @@ -3896,9 +3826,9 @@ LibraryManager.library = { while (_isspace({{{ makeGetValue('str', 0, 'i8') }}})) str++; // Check for a plus/minus sign. - if ({{{ makeGetValue('str', 0, 'i8') }}} == '-'.charCodeAt(0)) { + if ({{{ makeGetValue('str', 0, 'i8') }}} == {{{ charCode('-') }}}) { str++; - } else if ({{{ makeGetValue('str', 0, 'i8') }}} == '+'.charCodeAt(0)) { + } else if ({{{ makeGetValue('str', 0, 'i8') }}} == {{{ charCode('+') }}}) { str++; } @@ -3906,9 +3836,9 @@ LibraryManager.library = { var ok = false; var finalBase = base; if (!finalBase) { - if ({{{ makeGetValue('str', 0, 'i8') }}} == '0'.charCodeAt(0)) { - if ({{{ makeGetValue('str+1', 0, 'i8') }}} == 'x'.charCodeAt(0) || - {{{ makeGetValue('str+1', 0, 'i8') }}} == 'X'.charCodeAt(0)) { + if ({{{ makeGetValue('str', 0, 'i8') }}} == {{{ charCode('0') }}}) { + if ({{{ makeGetValue('str+1', 0, 'i8') }}} == {{{ charCode('x') }}} || + {{{ makeGetValue('str+1', 0, 'i8') }}} == {{{ charCode('X') }}}) { finalBase = 16; str += 2; } else { @@ -3971,11 +3901,6 @@ LibraryManager.library = { }, strtoull_l: 'strtoull', // no locale support yet - atof__deps: ['strtod'], - atof: function(ptr) { - return _strtod(ptr, null); - }, - atoi__deps: ['strtol'], atoi: function(ptr) { return _strtol(ptr, null, 10); @@ -4235,13 +4160,15 @@ LibraryManager.library = { memcpy__inline: function (dest, src, num, align) { var ret = ''; #if ASSERTIONS +#if ASM_JS == 0 ret += "assert(" + num + " % 1 === 0);"; //, 'memcpy given ' + " + num + " + ' bytes to copy. Problem with quantum=1 corrections perhaps?');"; #endif +#endif ret += makeCopyValues(dest, src, num, 'null', null, align); return ret; }, - memcpy__asm: 'true', + memcpy__asm: true, memcpy__sig: 'iiii', memcpy: function (dest, src, num) { dest = dest|0; src = src|0; num = num|0; @@ -4314,7 +4241,7 @@ LibraryManager.library = { ptr = ptr|0; value = value|0; num = num|0; var stop = 0, value4 = 0, stop4 = 0, unaligned = 0; stop = (ptr + num)|0; - if ((num|0) >= {{{ SEEK_OPTIMAL_ALIGN_MIN }}}) { + if ((num|0) >= {{{ Math.round(2.5*UNROLL_LOOP_MAX) }}}) { // This is unaligned, but quite large, so work hard to get to aligned settings value = value & 0xff; unaligned = ptr & 3; @@ -4402,14 +4329,18 @@ LibraryManager.library = { } }, + strcpy__asm: true, + strcpy__sig: 'iii', strcpy: function(pdest, psrc) { + pdest = pdest|0; psrc = psrc|0; var i = 0; do { - {{{ makeCopyValues('pdest+i', 'psrc+i', 1, 'i8', null, 1) }}}; - i ++; - } while ({{{ makeGetValue('psrc', 'i-1', 'i8') }}} != 0); - return pdest; + {{{ makeCopyValues('(pdest+i)|0', '(psrc+i)|0', 1, 'i8', null, 1) }}}; + i = (i+1)|0; + } while (({{{ makeGetValue('psrc', 'i-1', 'i8') }}})|0 != 0); + return pdest|0; }, + stpcpy: function(pdest, psrc) { var i = 0; do { @@ -4419,14 +4350,18 @@ LibraryManager.library = { return pdest + i - 1; }, + strncpy__asm: true, + strncpy__sig: 'iiii', strncpy: function(pdest, psrc, num) { - var padding = false, curr; - for (var i = 0; i < num; i++) { - curr = padding ? 0 : {{{ makeGetValue('psrc', 'i', 'i8') }}}; + pdest = pdest|0; psrc = psrc|0; num = num|0; + var padding = 0, curr = 0, i = 0; + while ((i|0) < (num|0)) { + curr = padding ? 0 : {{{ makeGetValueAsm('psrc', 'i', 'i8') }}}; {{{ makeSetValue('pdest', 'i', 'curr', 'i8') }}} - padding = padding || {{{ makeGetValue('psrc', 'i', 'i8') }}} == 0; + padding = padding ? 1 : ({{{ makeGetValueAsm('psrc', 'i', 'i8') }}} == 0); + i = (i+1)|0; } - return pdest; + return pdest|0; }, strlwr__deps:['tolower'], @@ -4451,15 +4386,18 @@ LibraryManager.library = { } }, + strcat__asm: true, + strcat__sig: 'iii', strcat__deps: ['strlen'], strcat: function(pdest, psrc) { - var len = _strlen(pdest); + pdest = pdest|0; psrc = psrc|0; var i = 0; + pdest = (pdest + _strlen(pdest))|0; do { - {{{ makeCopyValues('pdest+len+i', 'psrc+i', 1, 'i8', null, 1) }}}; - i ++; - } while ({{{ makeGetValue('psrc', 'i-1', 'i8') }}} != 0); - return pdest; + {{{ makeCopyValues('pdest+i', 'psrc+i', 1, 'i8', null, 1) }}}; + i = (i+1)|0; + } while ({{{ makeGetValueAsm('psrc', 'i-1', 'i8') }}} != 0); + return pdest|0; }, strncat__deps: ['strlen'], @@ -4485,9 +4423,12 @@ LibraryManager.library = { // We always assume ASCII locale. strcoll: 'strcmp', + strcasecmp__asm: true, + strcasecmp__sig: 'iii', strcasecmp__deps: ['strncasecmp'], strcasecmp: function(px, py) { - return _strncasecmp(px, py, TOTAL_MEMORY); + px = px|0; py = py|0; + return _strncasecmp(px, py, -1)|0; }, strncmp: function(px, py, n) { @@ -4508,26 +4449,29 @@ LibraryManager.library = { return 0; }, + strncasecmp__asm: true, + strncasecmp__sig: 'iiii', strncasecmp__deps: ['tolower'], strncasecmp: function(px, py, n) { - var i = 0; - while (i < n) { - var x = _tolower({{{ makeGetValue('px', 'i', 'i8', 0, 1) }}}); - var y = _tolower({{{ makeGetValue('py', 'i', 'i8', 0, 1) }}}); - if (x == y && x == 0) return 0; - if (x == 0) return -1; - if (y == 0) return 1; - if (x == y) { - i ++; + px = px|0; py = py|0; n = n|0; + var i = 0, x = 0, y = 0; + while ((i>>>0) < (n>>>0)) { + x = _tolower({{{ makeGetValueAsm('px', 'i', 'i8', 0, 1) }}}); + y = _tolower({{{ makeGetValueAsm('py', 'i', 'i8', 0, 1) }}}); + if (((x|0) == (y|0)) & ((x|0) == 0)) return 0; + if ((x|0) == 0) return -1; + if ((y|0) == 0) return 1; + if ((x|0) == (y|0)) { + i = (i + 1)|0; continue; } else { - return x > y ? 1 : -1; + return ((x>>>0) > (y>>>0) ? 1 : -1)|0; } } return 0; }, - memcmp__asm: 'true', + memcmp__asm: true, memcmp__sig: 'iiii', memcmp: function(p1, p2, num) { p1 = p1|0; p2 = p2|0; num = num|0; @@ -4729,58 +4673,61 @@ LibraryManager.library = { return chr & 0x7F; }, toupper: function(chr) { - if (chr >= 'a'.charCodeAt(0) && chr <= 'z'.charCodeAt(0)) { - return chr - 'a'.charCodeAt(0) + 'A'.charCodeAt(0); + if (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}) { + return chr - {{{ charCode('a') }}} + {{{ charCode('A') }}}; } else { return chr; } }, _toupper: 'toupper', + + tolower__asm: true, + tolower__sig: 'ii', tolower: function(chr) { - if (chr >= 'A'.charCodeAt(0) && chr <= 'Z'.charCodeAt(0)) { - return chr - 'A'.charCodeAt(0) + 'a'.charCodeAt(0); - } else { - return chr; - } + chr = chr|0; + if ((chr|0) < {{{ charCode('A') }}}) return chr|0; + if ((chr|0) > {{{ charCode('Z') }}}) return chr|0; + return (chr - {{{ charCode('A') }}} + {{{ charCode('a') }}})|0; }, _tolower: 'tolower', + // The following functions are defined as macros in glibc. islower: function(chr) { - return chr >= 'a'.charCodeAt(0) && chr <= 'z'.charCodeAt(0); + return chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}; }, isupper: function(chr) { - return chr >= 'A'.charCodeAt(0) && chr <= 'Z'.charCodeAt(0); + return chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('Z') }}}; }, isalpha: function(chr) { - return (chr >= 'a'.charCodeAt(0) && chr <= 'z'.charCodeAt(0)) || - (chr >= 'A'.charCodeAt(0) && chr <= 'Z'.charCodeAt(0)); + return (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}) || + (chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('Z') }}}); }, isdigit: function(chr) { - return chr >= '0'.charCodeAt(0) && chr <= '9'.charCodeAt(0); + return chr >= {{{ charCode('0') }}} && chr <= {{{ charCode('9') }}}; }, isdigit_l: 'isdigit', // no locale support yet isxdigit: function(chr) { - return (chr >= '0'.charCodeAt(0) && chr <= '9'.charCodeAt(0)) || - (chr >= 'a'.charCodeAt(0) && chr <= 'f'.charCodeAt(0)) || - (chr >= 'A'.charCodeAt(0) && chr <= 'F'.charCodeAt(0)); + return (chr >= {{{ charCode('0') }}} && chr <= {{{ charCode('9') }}}) || + (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('f') }}}) || + (chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('F') }}}); }, isxdigit_l: 'isxdigit', // no locale support yet isalnum: function(chr) { - return (chr >= '0'.charCodeAt(0) && chr <= '9'.charCodeAt(0)) || - (chr >= 'a'.charCodeAt(0) && chr <= 'z'.charCodeAt(0)) || - (chr >= 'A'.charCodeAt(0) && chr <= 'Z'.charCodeAt(0)); + return (chr >= {{{ charCode('0') }}} && chr <= {{{ charCode('9') }}}) || + (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}) || + (chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('Z') }}}); }, ispunct: function(chr) { - return (chr >= '!'.charCodeAt(0) && chr <= '/'.charCodeAt(0)) || - (chr >= ':'.charCodeAt(0) && chr <= '@'.charCodeAt(0)) || - (chr >= '['.charCodeAt(0) && chr <= '`'.charCodeAt(0)) || - (chr >= '{'.charCodeAt(0) && chr <= '~'.charCodeAt(0)); + return (chr >= {{{ charCode('!') }}} && chr <= {{{ charCode('/') }}}) || + (chr >= {{{ charCode(':') }}} && chr <= {{{ charCode('@') }}}) || + (chr >= {{{ charCode('[') }}} && chr <= {{{ charCode('`') }}}) || + (chr >= {{{ charCode('{') }}} && chr <= {{{ charCode('~') }}}); }, isspace: function(chr) { return chr in { 32: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0 }; }, isblank: function(chr) { - return chr == ' '.charCodeAt(0) || chr == '\t'.charCodeAt(0); + return chr == {{{ charCode(' ') }}} || chr == {{{ charCode('\t') }}}; }, iscntrl: function(chr) { return (0 <= chr && chr <= 0x1F) || chr === 0x7F; @@ -4976,6 +4923,20 @@ LibraryManager.library = { #endif }, + llvm_ctpop_i32: function(x) { + var ret = 0; + while (x) { + if (x&1) ret++; + x >>= 1; + } + return ret; + }, + + llvm_ctpop_i64__deps: ['llvm_ctpop_i32'], + llvm_ctpop_i64: function(l, h) { + return _llvm_ctpop_i32(l) + _llvm_ctpop_i32(h); + }, + llvm_trap: function() { throw 'trap! ' + new Error().stack; }, @@ -5270,13 +5231,6 @@ LibraryManager.library = { {{{ makeStructuralReturn(['(x*y)>>>0', 'x*y > 4294967295']) }}}; }, - llvm_uadd_with_overflow_i64__deps: [function() { Types.preciseI64MathUsed = 1 }], - llvm_uadd_with_overflow_i64: function(xl, xh, yl, yh) { - i64Math.add(xl, xh, yl, yh); - {{{ makeStructuralReturn([makeGetTempDouble(0, 'i32'), makeGetTempDouble(1, 'i32'), '0']) }}}; - // XXX Need to hack support for second param in long.js - }, - llvm_umul_with_overflow_i64__deps: [function() { Types.preciseI64MathUsed = 1 }], llvm_umul_with_overflow_i64: function(xl, xh, yl, yh) { i64Math.multiply(xl, xh, yl, yh); @@ -5506,6 +5460,14 @@ LibraryManager.library = { return -a; }, copysignf: 'copysign', + __signbit__deps: ['copysign'], + __signbit: function(x) { + // We implement using copysign so that we get support + // for negative zero (once copysign supports that). + return _copysign(1.0, x) < 0; + }, + __signbitf: '__signbit', + __signbitd: '__signbit', hypot: function(a, b) { return Math.sqrt(a*a + b*b); }, @@ -6064,6 +6026,15 @@ LibraryManager.library = { __timespec_struct_layout: Runtime.generateStructInfo([ ['i32', 'tv_sec'], ['i32', 'tv_nsec']]), + nanosleep__deps: ['usleep', '__timespec_struct_layout'], + nanosleep: function(rqtp, rmtp) { + // int nanosleep(const struct timespec *rqtp, struct timespec *rmtp); + var seconds = {{{ makeGetValue('rqtp', '___timespec_struct_layout.tv_sec', 'i32') }}}; + var nanoseconds = {{{ makeGetValue('rqtp', '___timespec_struct_layout.tv_nsec', 'i32') }}}; + {{{ makeSetValue('rmtp', '___timespec_struct_layout.tv_sec', '0', 'i32') }}} + {{{ makeSetValue('rmtp', '___timespec_struct_layout.tv_nsec', '0', 'i32') }}} + return _usleep((seconds * 1e6) + (nanoseconds / 1000)); + }, // TODO: Implement these for real. clock_gettime__deps: ['__timespec_struct_layout'], clock_gettime: function(clk_id, tp) { @@ -6170,7 +6141,7 @@ LibraryManager.library = { setjmp__inline: function(env) { // Save the label - return '(tempInt = setjmpId++, mySetjmpIds[tempInt] = 1, setjmpLabels[tempInt] = label,' + makeSetValue(env, '0', 'tempInt', 'i32') + ', 0)'; + return '(tempInt = setjmpId++, mySetjmpIds[tempInt] = 1, setjmpLabels[tempInt] = label,' + makeSetValue(env, '0', 'tempInt', 'i32', undefined, undefined, undefined, undefined, ',') + ', 0)'; }, longjmp: function(env, value) { @@ -6693,6 +6664,13 @@ LibraryManager.library = { }, // ========================================================================== + // sched.h (stubs only - no thread support yet!) + // ========================================================================== + sched_yield: function() { + return 0; + }, + + // ========================================================================== // pthread.h (stubs for mutexes only - no thread support yet!) // ========================================================================== @@ -6708,8 +6686,15 @@ LibraryManager.library = { }, pthread_cond_init: function() {}, pthread_cond_destroy: function() {}, - pthread_cond_broadcast: function() {}, - pthread_cond_wait: function() {}, + pthread_cond_broadcast: function() { + return 0; + }, + pthread_cond_wait: function() { + return 0; + }, + pthread_cond_timedwait: function() { + return 0; + }, pthread_self: function() { //FIXME: assumes only a single thread return 0; @@ -7385,6 +7370,81 @@ LibraryManager.library = { Module.print(intArrayToString(__formatString(_emscripten_jcache_printf_.buffer, varargs + i*4)).replace('\\n', '')); Runtime.stackAlloc(-4*i); // free up the stack space we know is ok to free }, + + //============================ + // i64 math + //============================ + + i64Add__asm: true, + i64Add__sig: 'iiiii', + i64Add: function(a, b, c, d) { + /* + x = a + b*2^32 + y = c + d*2^32 + result = l + h*2^32 + */ + a = a|0; b = b|0; c = c|0; d = d|0; + var l = 0, h = 0; + l = (a + c)>>>0; + h = (b + d)>>>0; + if ((l>>>0) < (a>>>0)) { // iff we overflowed + h = (h+1)>>>0; + } + {{{ makeStructuralReturn(['l|0', 'h'], true) }}}; + }, + llvm_uadd_with_overflow_i64__asm: true, + llvm_uadd_with_overflow_i64__sig: 'iiiii', + llvm_uadd_with_overflow_i64: function(a, b, c, d) { + a = a|0; b = b|0; c = c|0; d = d|0; + var l = 0, h = 0, overflow = 0; + l = (a + c)>>>0; + h = (b + d)>>>0; + if ((l>>>0) < (a>>>0)) { // iff we overflowed + h = (h+1)>>>0; + overflow = 1; + } + {{{ makeStructuralReturn(['l|0', 'h', 'overflow'], true) }}}; + }, + + bitshift64Shl__asm: true, + bitshift64Shl__sig: 'iiii', + bitshift64Shl: function(low, high, bits) { + low = low|0; high = high|0; bits = bits|0; + var ander = 0; + if ((bits|0) < 32) { + ander = ((1 << bits) - 1)|0; + tempRet0 = (high << bits) | ((low&(ander << (32 - bits))) >>> (32 - bits)); + return low << bits; + } + tempRet0 = low << (bits - 32); + return 0; + }, + bitshift64Ashr__asm: true, + bitshift64Ashr__sig: 'iiii', + bitshift64Ashr: function(low, high, bits) { + low = low|0; high = high|0; bits = bits|0; + var ander = 0; + if ((bits|0) < 32) { + ander = ((1 << bits) - 1)|0; + tempRet0 = high >> bits; + return (low >>> bits) | ((high&ander) << (32 - bits)); + } + tempRet0 = (high|0) < 0 ? -1 : 0; + return (high >> (bits - 32))|0; + }, + bitshift64Lshr__asm: true, + bitshift64Lshr__sig: 'iiii', + bitshift64Lshr: function(low, high, bits) { + low = low|0; high = high|0; bits = bits|0; + var ander = 0; + if ((bits|0) < 32) { + ander = ((1 << bits) - 1)|0; + tempRet0 = high >>> bits; + return (low >>> bits) | ((high&ander) << (32 - bits)); + } + tempRet0 = 0; + return (high >>> (bits - 32))|0; + }, }; function autoAddDeps(object, name) { @@ -7396,3 +7456,4 @@ function autoAddDeps(object, name) { } } + diff --git a/src/library_browser.js b/src/library_browser.js index 5b19a360..bdd94bac 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -3,7 +3,7 @@ // Utilities for browser environments mergeInto(LibraryManager.library, { - $Browser__postset: 'Module["requestFullScreen"] = function() { Browser.requestFullScreen() };\n' + // exports + $Browser__postset: 'Module["requestFullScreen"] = function(lockPointer, resizeCanvas) { Browser.requestFullScreen(lockPointer, resizeCanvas) };\n' + // exports 'Module["requestAnimationFrame"] = function(func) { Browser.requestAnimationFrame(func) };\n' + 'Module["pauseMainLoop"] = function() { Browser.mainLoop.pause() };\n' + 'Module["resumeMainLoop"] = function() { Browser.mainLoop.resume() };\n', @@ -40,6 +40,7 @@ mergeInto(LibraryManager.library, { } } }, + isFullScreen: false, pointerLock: false, moduleContextCreatedCallbacks: [], workers: [], @@ -205,10 +206,10 @@ mergeInto(LibraryManager.library, { try { if (useWebGL) { ctx = canvas.getContext('experimental-webgl', { - alpha: false, #if GL_TESTING - preserveDrawingBuffer: true + preserveDrawingBuffer: true, #endif + alpha: false }); } else { ctx = canvas.getContext('2d'); @@ -273,36 +274,60 @@ mergeInto(LibraryManager.library, { } return ctx; }, + destroyContext: function(canvas, useWebGL, setInModule) {}, - requestFullScreen: function() { + + fullScreenHandlersInstalled: false, + lockPointer: undefined, + resizeCanvas: undefined, + requestFullScreen: function(lockPointer, resizeCanvas) { + this.lockPointer = lockPointer; + this.resizeCanvas = resizeCanvas; + if (typeof this.lockPointer === 'undefined') this.lockPointer = true; + if (typeof this.resizeCanvas === 'undefined') this.resizeCanvas = false; + var canvas = Module['canvas']; function fullScreenChange() { - var isFullScreen = false; + Browser.isFullScreen = false; if ((document['webkitFullScreenElement'] || document['webkitFullscreenElement'] || document['mozFullScreenElement'] || document['mozFullscreenElement'] || document['fullScreenElement'] || document['fullscreenElement']) === canvas) { canvas.requestPointerLock = canvas['requestPointerLock'] || canvas['mozRequestPointerLock'] || canvas['webkitRequestPointerLock']; - canvas.requestPointerLock(); - isFullScreen = true; + canvas.exitPointerLock = document['exitPointerLock'] || + document['mozExitPointerLock'] || + document['webkitExitPointerLock']; + canvas.exitPointerLock = canvas.exitPointerLock.bind(document); + canvas.cancelFullScreen = document['cancelFullScreen'] || + document['mozCancelFullScreen'] || + document['webkitCancelFullScreen']; + canvas.cancelFullScreen = canvas.cancelFullScreen.bind(document); + if (Browser.lockPointer) canvas.requestPointerLock(); + Browser.isFullScreen = true; + if (Browser.resizeCanvas) Browser.setFullScreenCanvasSize(); + } else if (Browser.resizeCanvas){ + Browser.setWindowedCanvasSize(); } - if (Module['onFullScreen']) Module['onFullScreen'](isFullScreen); + if (Module['onFullScreen']) Module['onFullScreen'](Browser.isFullScreen); } - document.addEventListener('fullscreenchange', fullScreenChange, false); - document.addEventListener('mozfullscreenchange', fullScreenChange, false); - document.addEventListener('webkitfullscreenchange', fullScreenChange, false); - function pointerLockChange() { Browser.pointerLock = document['pointerLockElement'] === canvas || document['mozPointerLockElement'] === canvas || document['webkitPointerLockElement'] === canvas; } - document.addEventListener('pointerlockchange', pointerLockChange, false); - document.addEventListener('mozpointerlockchange', pointerLockChange, false); - document.addEventListener('webkitpointerlockchange', pointerLockChange, false); + if (!this.fullScreenHandlersInstalled) { + this.fullScreenHandlersInstalled = true; + document.addEventListener('fullscreenchange', fullScreenChange, false); + document.addEventListener('mozfullscreenchange', fullScreenChange, false); + document.addEventListener('webkitfullscreenchange', fullScreenChange, false); + + document.addEventListener('pointerlockchange', pointerLockChange, false); + document.addEventListener('mozpointerlockchange', pointerLockChange, false); + document.addEventListener('webkitpointerlockchange', pointerLockChange, false); + } canvas.requestFullScreen = canvas['requestFullScreen'] || canvas['mozRequestFullScreen'] || @@ -380,7 +405,32 @@ mergeInto(LibraryManager.library, { canvas.width = width; canvas.height = height; if (!noUpdates) Browser.updateResizeListeners(); + }, + + windowedWidth: 0, + windowedHeight: 0, + setFullScreenCanvasSize: function() { + var canvas = Module['canvas']; + this.windowedWidth = canvas.width; + this.windowedHeight = canvas.height; + canvas.width = screen.width; + canvas.height = screen.height; + var flags = {{{ makeGetValue('SDL.screen+Runtime.QUANTUM_SIZE*0', '0', 'i32', 0, 1) }}}; + flags = flags | 0x00800000; // set SDL_FULLSCREEN flag + {{{ makeSetValue('SDL.screen+Runtime.QUANTUM_SIZE*0', '0', 'flags', 'i32') }}} + Browser.updateResizeListeners(); + }, + + setWindowedCanvasSize: function() { + var canvas = Module['canvas']; + canvas.width = this.windowedWidth; + canvas.height = this.windowedHeight; + var flags = {{{ makeGetValue('SDL.screen+Runtime.QUANTUM_SIZE*0', '0', 'i32', 0, 1) }}}; + flags = flags & ~0x00800000; // clear SDL_FULLSCREEN flag + {{{ makeSetValue('SDL.screen+Runtime.QUANTUM_SIZE*0', '0', 'flags', 'i32') }}} + Browser.updateResizeListeners(); } + }, emscripten_async_wget: function(url, file, onload, onerror) { diff --git a/src/library_gl.js b/src/library_gl.js index 4977d2e9..9e12e4ee 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -21,7 +21,6 @@ var LibraryGL = { #if FULL_ES2 clientBuffers: [], - enabledClientBuffers: [], #endif currArrayBuffer: 0, currElementArrayBuffer: 0, @@ -59,6 +58,65 @@ var LibraryGL = { return ret; }, + // Temporary buffers + MAX_TEMP_BUFFER_SIZE: {{{ GL_MAX_TEMP_BUFFER_SIZE }}}, + tempBufferIndexLookup: null, + tempVertexBuffers: null, + tempIndexBuffers: null, + tempQuadIndexBuffer: null, + + generateTempBuffers: function(quads) { + this.tempBufferIndexLookup = new Uint8Array(this.MAX_TEMP_BUFFER_SIZE+1); + this.tempVertexBuffers = []; + this.tempIndexBuffers = []; + var last = -1, curr = -1; + var size = 1; + for (var i = 0; i <= this.MAX_TEMP_BUFFER_SIZE; i++) { + if (i > size) { + size <<= 1; + } + if (size != last) { + curr++; + this.tempVertexBuffers[curr] = Module.ctx.createBuffer(); + Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, this.tempVertexBuffers[curr]); + Module.ctx.bufferData(Module.ctx.ARRAY_BUFFER, size, Module.ctx.DYNAMIC_DRAW); + Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, null); + this.tempIndexBuffers[curr] = Module.ctx.createBuffer(); + Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, this.tempIndexBuffers[curr]); + Module.ctx.bufferData(Module.ctx.ELEMENT_ARRAY_BUFFER, size, Module.ctx.DYNAMIC_DRAW); + Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, null); + last = size; + } + this.tempBufferIndexLookup[i] = curr; + } + + if (quads) { + // GL_QUAD indexes can be precalculated + this.tempQuadIndexBuffer = Module.ctx.createBuffer(); + Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, this.tempQuadIndexBuffer); + var numIndexes = this.MAX_TEMP_BUFFER_SIZE >> 1; + var quadIndexes = new Uint16Array(numIndexes); + var i = 0, v = 0; + while (1) { + quadIndexes[i++] = v; + if (i >= numIndexes) break; + quadIndexes[i++] = v+1; + if (i >= numIndexes) break; + quadIndexes[i++] = v+2; + if (i >= numIndexes) break; + quadIndexes[i++] = v; + if (i >= numIndexes) break; + quadIndexes[i++] = v+2; + if (i >= numIndexes) break; + quadIndexes[i++] = v+3; + if (i >= numIndexes) break; + v += 4; + } + Module.ctx.bufferData(Module.ctx.ELEMENT_ARRAY_BUFFER, quadIndexes, Module.ctx.STATIC_DRAW); + Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, null); + } + }, + // Linear lookup in one of the tables (buffers, programs, etc.). TODO: consider using a weakmap to make this faster, if it matters scan: function(table, object) { for (var item in table) { @@ -207,20 +265,35 @@ var LibraryGL = { return size * typeSize * count; }, + usedTempBuffers: [], + preDrawHandleClientVertexAttribBindings: function(count) { GL.resetBufferBinding = false; + + var used = GL.usedTempBuffers; + used.length = 0; + + // TODO: initial pass to detect ranges we need to upload, might not need an upload per attrib for (var i = 0; i < GL.maxVertexAttribs; ++i) { - if (!GL.enabledClientBuffers[i] || !GL.clientBuffers[i]) continue; - - GL.resetBufferBinding = true; - var cb = GL.clientBuffers[i]; - - var buf = Module.ctx.createBuffer(); + if (!cb.clientside || !cb.enabled) continue; + + GL.resetBufferBinding = true; + + var size = GL.calcBufLength(cb.size, cb.type, cb.stride, count); + var index = GL.tempBufferIndexLookup[size]; + var buf; + do { +#if ASSERTIONS + assert(index < GL.tempVertexBuffers.length); +#endif + buf = GL.tempVertexBuffers[index++]; + } while (used.indexOf(buf) >= 0); + used.push(buf); Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, buf); - Module.ctx.bufferData(Module.ctx.ARRAY_BUFFER, - HEAPU8.subarray(cb.ptr, cb.ptr + GL.calcBufLength(cb.size, cb.type, cb.stride, count)), - Module.ctx.DYNAMIC_DRAW); + Module.ctx.bufferSubData(Module.ctx.ARRAY_BUFFER, + 0, + HEAPU8.subarray(cb.ptr, cb.ptr + size)); Module.ctx.vertexAttribPointer(i, cb.size, cb.type, cb.normalized, cb.stride, 0); } }, @@ -239,6 +312,13 @@ var LibraryGL = { if (!Module.useWebGL) return; // an app might link both gl and 2d backends GL.maxVertexAttribs = Module.ctx.getParameter(Module.ctx.MAX_VERTEX_ATTRIBS); +#if FULL_ES2 + for (var i = 0; i < GL.maxVertexAttribs; i++) { + GL.clientBuffers[i] = { enabled: false, clientside: false, size: 0, type: 0, normalized: 0, stride: 0, ptr: 0 }; + } + + GL.generateTempBuffers(); +#endif GL.compressionExt = Module.ctx.getExtension('WEBGL_compressed_texture_s3tc') || Module.ctx.getExtension('MOZ_WEBGL_compressed_texture_s3tc') || @@ -612,7 +692,7 @@ var LibraryGL = { glGetVertexAttribfv: function(index, pname, params) { #if FULL_ES2 - if (GL.clientBuffers[index]) { + if (GL.clientBuffers[index].enabled) { Module.printErr("glGetVertexAttribfv on client-side array: not supported, bad data returned"); } #endif @@ -628,7 +708,7 @@ var LibraryGL = { glGetVertexAttribiv: function(index, pname, params) { #if FULL_ES2 - if (GL.clientBuffers[index]) { + if (GL.clientBuffers[index].enabled) { Module.printErr("glGetVertexAttribiv on client-side array: not supported, bad data returned"); } #endif @@ -644,7 +724,7 @@ var LibraryGL = { glGetVertexAttribPointerv: function(index, pname, pointer) { #if FULL_ES2 - if (GL.clientBuffers[index]) { + if (GL.clientBuffers[index].enabled) { Module.printErr("glGetVertexAttribPointer on client-side array: not supported, bad data returned"); } #endif @@ -1693,62 +1773,6 @@ var LibraryGL = { this.modifiedClientAttributes = true; }, - // Temporary buffers - MAX_TEMP_BUFFER_SIZE: {{{ GL_MAX_TEMP_BUFFER_SIZE }}}, - tempBufferIndexLookup: null, - tempVertexBuffers: null, - tempIndexBuffers: null, - tempQuadIndexBuffer: null, - - generateTempBuffers: function() { - this.tempBufferIndexLookup = new Uint8Array(this.MAX_TEMP_BUFFER_SIZE+1); - this.tempVertexBuffers = []; - this.tempIndexBuffers = []; - var last = -1, curr = -1; - var size = 1; - for (var i = 0; i <= this.MAX_TEMP_BUFFER_SIZE; i++) { - if (i > size) { - size <<= 1; - } - if (size != last) { - curr++; - this.tempVertexBuffers[curr] = Module.ctx.createBuffer(); - Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, this.tempVertexBuffers[curr]); - Module.ctx.bufferData(Module.ctx.ARRAY_BUFFER, size, Module.ctx.DYNAMIC_DRAW); - Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, null); - this.tempIndexBuffers[curr] = Module.ctx.createBuffer(); - Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, this.tempIndexBuffers[curr]); - Module.ctx.bufferData(Module.ctx.ELEMENT_ARRAY_BUFFER, size, Module.ctx.DYNAMIC_DRAW); - Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, null); - last = size; - } - this.tempBufferIndexLookup[i] = curr; - } - // GL_QUAD indexes can be precalculated - this.tempQuadIndexBuffer = Module.ctx.createBuffer(); - Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, this.tempQuadIndexBuffer); - var numIndexes = this.MAX_TEMP_BUFFER_SIZE >> 1; - var quadIndexes = new Uint16Array(numIndexes); - var i = 0, v = 0; - while (1) { - quadIndexes[i++] = v; - if (i >= numIndexes) break; - quadIndexes[i++] = v+1; - if (i >= numIndexes) break; - quadIndexes[i++] = v+2; - if (i >= numIndexes) break; - quadIndexes[i++] = v; - if (i >= numIndexes) break; - quadIndexes[i++] = v+2; - if (i >= numIndexes) break; - quadIndexes[i++] = v+3; - if (i >= numIndexes) break; - v += 4; - } - Module.ctx.bufferData(Module.ctx.ELEMENT_ARRAY_BUFFER, quadIndexes, Module.ctx.STATIC_DRAW); - Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, null); - }, - // Renderers addRendererComponent: function(name, size, type) { if (!this.rendererComponents[name]) { @@ -1969,8 +1993,8 @@ var LibraryGL = { if (!GL.currArrayBuffer) { var start = GL.immediate.firstVertex*GL.immediate.stride; var end = GL.immediate.lastVertex*GL.immediate.stride; - assert(end <= GL.immediate.MAX_TEMP_BUFFER_SIZE, 'too much vertex data'); - arrayBuffer = GL.immediate.tempVertexBuffers[GL.immediate.tempBufferIndexLookup[end]]; + assert(end <= GL.MAX_TEMP_BUFFER_SIZE, 'too much vertex data'); + arrayBuffer = GL.tempVertexBuffers[GL.tempBufferIndexLookup[end]]; // TODO: consider using the last buffer we bound, if it was larger. downside is larger buffer, but we might avoid rebinding and preparing } else { arrayBuffer = GL.currArrayBuffer; @@ -2161,12 +2185,12 @@ var LibraryGL = { this.rendererCache = this.rendererCacheItemTemplate.slice(); // Buffers for data - this.tempData = new Float32Array(this.MAX_TEMP_BUFFER_SIZE >> 2); - this.indexData = new Uint16Array(this.MAX_TEMP_BUFFER_SIZE >> 1); + this.tempData = new Float32Array(GL.MAX_TEMP_BUFFER_SIZE >> 2); + this.indexData = new Uint16Array(GL.MAX_TEMP_BUFFER_SIZE >> 1); this.vertexDataU8 = new Uint8Array(this.tempData.buffer); - this.generateTempBuffers(); + GL.generateTempBuffers(true); this.clientColor = new Float32Array([1, 1, 1, 1]); }, @@ -2209,7 +2233,7 @@ var LibraryGL = { #if ASSERTIONS Runtime.warnOnce('Unpacking/restriding attributes, this is not fast'); #endif - if (!GL.immediate.restrideBuffer) GL.immediate.restrideBuffer = _malloc(GL.immediate.MAX_TEMP_BUFFER_SIZE); + if (!GL.immediate.restrideBuffer) GL.immediate.restrideBuffer = _malloc(GL.MAX_TEMP_BUFFER_SIZE); start = GL.immediate.restrideBuffer; #if ASSERTIONS assert(start % 4 == 0); @@ -2224,7 +2248,7 @@ var LibraryGL = { bytes += size; } #if ASSERTIONS - assert(count*bytes <= GL.immediate.MAX_TEMP_BUFFER_SIZE); + assert(count*bytes <= GL.MAX_TEMP_BUFFER_SIZE); #endif // copy out the data (we need to know the stride for that, and define attribute.pointer for (var i = 0; i < attributes.length; i++) { @@ -2298,8 +2322,8 @@ var LibraryGL = { } if (!GL.currElementArrayBuffer) { // If no element array buffer is bound, then indices is a literal pointer to clientside data - assert(numProvidedIndexes << 1 <= GL.immediate.MAX_TEMP_BUFFER_SIZE, 'too many immediate mode indexes (a)'); - var indexBuffer = GL.immediate.tempIndexBuffers[GL.immediate.tempBufferIndexLookup[numProvidedIndexes << 1]]; + assert(numProvidedIndexes << 1 <= GL.MAX_TEMP_BUFFER_SIZE, 'too many immediate mode indexes (a)'); + var indexBuffer = GL.tempIndexBuffers[GL.tempBufferIndexLookup[numProvidedIndexes << 1]]; Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, indexBuffer); Module.ctx.bufferSubData(Module.ctx.ELEMENT_ARRAY_BUFFER, 0, {{{ makeHEAPView('U16', 'ptr', 'ptr + (numProvidedIndexes << 1)') }}}); ptr = 0; @@ -2314,8 +2338,8 @@ var LibraryGL = { ptr = GL.immediate.firstVertex*3; var numQuads = numVertexes / 4; numIndexes = numQuads * 6; // 0 1 2, 0 2 3 pattern - assert(ptr + (numIndexes << 1) <= GL.immediate.MAX_TEMP_BUFFER_SIZE, 'too many immediate mode indexes (b)'); - Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, this.tempQuadIndexBuffer); + assert(ptr + (numIndexes << 1) <= GL.MAX_TEMP_BUFFER_SIZE, 'too many immediate mode indexes (b)'); + Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, GL.tempQuadIndexBuffer); emulatedElementArrayBuffer = true; } @@ -2369,7 +2393,7 @@ var LibraryGL = { GL.immediate.vertexData[GL.immediate.vertexCounter++] = y; GL.immediate.vertexData[GL.immediate.vertexCounter++] = z || 0; #if ASSERTIONS - assert(GL.immediate.vertexCounter << 2 < GL.immediate.MAX_TEMP_BUFFER_SIZE); + assert(GL.immediate.vertexCounter << 2 < GL.MAX_TEMP_BUFFER_SIZE); #endif GL.immediate.addRendererComponent(GL.immediate.VERTEX, 3, Module.ctx.FLOAT); }, @@ -2849,15 +2873,25 @@ var LibraryGL = { glShadeModel: function() { Runtime.warnOnce('TODO: glShadeModel') }, + // GLES2 emulation + glVertexAttribPointer__sig: 'viiiiii', glVertexAttribPointer: function(index, size, type, normalized, stride, ptr) { #if FULL_ES2 + var cb = GL.clientBuffers[index]; +#if ASSERTIONS + assert(cb, index); +#endif if (!GL.currArrayBuffer) { - GL.clientBuffers[index] = { size: size, type: type, normalized: normalized, stride: stride, ptr: ptr }; + cb.size = size; + cb.type = type; + cb.normalized = normalized; + cb.stride = stride; + cb.ptr = ptr; + cb.clientside = true; return; } - - GL.clientBuffers[index] = null; + cb.clientside = false; #endif Module.ctx.vertexAttribPointer(index, size, type, normalized, stride, ptr); }, @@ -2865,7 +2899,11 @@ var LibraryGL = { glEnableVertexAttribArray__sig: 'vi', glEnableVertexAttribArray: function(index) { #if FULL_ES2 - GL.enabledClientBuffers[index] = true; + var cb = GL.clientBuffers[index]; +#if ASSERTIONS + assert(cb, index); +#endif + cb.enabled = true; #endif Module.ctx.enableVertexAttribArray(index); }, @@ -2873,7 +2911,11 @@ var LibraryGL = { glDisableVertexAttribArray__sig: 'vi', glDisableVertexAttribArray: function(index) { #if FULL_ES2 - GL.enabledClientBuffers[index] = false; + var cb = GL.clientBuffers[index]; +#if ASSERTIONS + assert(cb, index); +#endif + cb.enabled = false; #endif Module.ctx.disableVertexAttribArray(index); }, @@ -2881,7 +2923,7 @@ var LibraryGL = { glDrawArrays: function(mode, first, count) { #if FULL_ES2 // bind any client-side buffers - GL.preDrawHandleClientVertexAttribBindings(count); + GL.preDrawHandleClientVertexAttribBindings(first + count); #endif Module.ctx.drawArrays(mode, first, count); @@ -2895,11 +2937,12 @@ var LibraryGL = { #if FULL_ES2 var buf; if (!GL.currElementArrayBuffer) { - buf = Module.ctx.createBuffer(); + var size = GL.calcBufLength(1, type, 0, count); + buf = GL.tempIndexBuffers[GL.tempBufferIndexLookup[size]]; Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, buf); - Module.ctx.bufferData(Module.ctx.ELEMENT_ARRAY_BUFFER, - HEAPU8.subarray(indices, indices + GL.calcBufLength(1, type, 0, count)), - Module.ctx.DYNAMIC_DRAW); + Module.ctx.bufferSubData(Module.ctx.ELEMENT_ARRAY_BUFFER, + 0, + HEAPU8.subarray(indices, indices + size)); // the index is now 0 indices = 0; } @@ -2915,17 +2958,18 @@ var LibraryGL = { if (!GL.currElementArrayBuffer) { Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, null); - Module.ctx.deleteBuffer(buf); } #endif }, // signatures of simple pass-through functions, see later + glActiveTexture__sig: 'vi', glCheckFramebufferStatus__sig: 'ii', glRenderbufferStorage__sig: 'viiii', // Open GLES1.1 compatibility + glGenFramebuffersOES : 'glGenFramebuffers', glGenRenderbuffersOES : 'glGenRenderbuffers', glBindFramebufferOES : 'glBindFramebuffer', diff --git a/src/library_openal.js b/src/library_openal.js new file mode 100644 index 00000000..5511ccb7 --- /dev/null +++ b/src/library_openal.js @@ -0,0 +1,471 @@ +//"use strict"; + +var LibraryOpenAL = { + $AL__deps: ['$Browser'], + $AL: { + contexts: [], + currentContext: null, + }, + + alcProcessContext: function(context) {}, + alcSuspendContext: function(context) {}, + + alcMakeContextCurrent: function(context) { + if (context == 0) { + AL.currentContext = null; + } else { + AL.currentContext = AL.contexts[context - 1]; + } + }, + + alcDestroyContext: function(context) { + // Stop playback, etc + }, + + alcCloseDevice: function(device) { + // Stop playback, etc + }, + + alcOpenDevice: function(deviceName) { + if (typeof(AudioContext) == "function" || + typeof(webkitAudioContext) == "function") { + return 1; // non-null pointer -- we just simulate one device + } else { + return 0; + } + }, + + alcCreateContext: function(device, attrList) { + if (device != 1) { + return 0; + } + + if (attrList) { + console.log("The attrList argument of alcCreateContext is not supported yet"); + return 0; + } + + var ctx; + try { + ctx = new AudioContext(); + } catch (e) { + try { + ctx = new webkitAudioContext(); + } catch (e) {} + } + + if (ctx) { + AL.contexts.push({ctx: ctx, err: 0, src: [], buf: []}); + return AL.contexts.length; + } else { + return 0; + } + }, + + alGetError: function() { + if (!AL.currentContext) { + return 0xA004 /* AL_INVALID_OPERATION */; + } else { + return AL.currentContext.err; + } + }, + + alDeleteSources: function(count, sources) + { + if (!AL.currentContext) { + console.error("alDeleteSources called without a valid context"); + return; + } + for (var i = 0; i < count; ++i) { + var sourceIdx = {{{ makeGetValue('sources', 'i', 'i32') }}} - 1; + delete AL.currentContext.src[sourceIdx]; + } + }, + + alGenSources: function(count, sources) { + if (!AL.currentContext) { + console.error("alGenSources called without a valid context"); + return; + } + for (var i = 0; i < count; ++i) { + var gain = AL.currentContext.ctx.createGain(); + var panner = AL.currentContext.ctx.createPanner(); + if (typeof(webkitAudioContext) == 'function') { + gain.connect(panner); + panner.connect(AL.currentContext.ctx.destination); + } else { + // Work around a Firefox bug (bug 849916) + gain.connect(AL.currentContext.ctx.destination); + } + gain.gain.value = 1; // work around a Firefox bug (bug 850970) + AL.currentContext.src.push({ + loop: false, + buffer: null, + gain: gain, + panner: panner, + paused: false, + playTime: -1, + pausedTime: 0 + }); + {{{ makeSetValue('sources', 'i', 'AL.currentContext.src.length', 'i32') }}}; + } + }, + + alSourcei: function(source, param, value) { + if (!AL.currentContext) { + console.error("alSourcei called without a valid context"); + return; + } + if (source > AL.currentContext.src.length) { + console.error("alSourcei called with an invalid source"); + return; + } + switch (param) { + case 0x1007 /* AL_LOOPING */: + AL.currentContext.src[source - 1].loop = (value != 0 /* AL_FALSE */); + break; + case 0x1009 /* AL_BUFFER */: + if (value == 0) { + AL.currentContext.src[source - 1].buffer = null; + } else { + AL.currentContext.src[source - 1].buffer = AL.currentContext.buf[value - 1].buf; + } + break; + default: + console.log("alSourcei with param " + param + " not implemented yet"); + break; + } + }, + + alSourcef: function(source, param, value) { + if (!AL.currentContext) { + consoue.error("alSourcef called without a valid context"); + return; + } + if (source > AL.currentContext.src.length) { + console.error("alSourcef called with an invalid source"); + return; + } + switch (param) { + case 0x100A /* AL_GAIN */: + AL.currentContext.src[source - 1].gain.gain.value = value; + break; + case 0x1003 /* AL_PITCH */: + console.log("alSourcef was called with AL_PITCH, but Web Audio does not support static pitch changes"); + break; + default: + console.log("alSourcef with param " + param + " not implemented yet"); + break; + } + }, + + alSourcefv: function(source, param, value) { + if (!AL.currentContext) { + consoue.error("alSourcefv called without a valid context"); + return; + } + if (source > AL.currentContext.src.length) { + console.error("alSourcefv called with an invalid source"); + return; + } + switch (param) { + case 0x1004 /* AL_POSITION */: + AL.currentContext.src[source - 1].panner.setPosition( + {{{ makeGetValue('value', '0', 'float') }}}, + {{{ makeGetValue('value', '1', 'float') }}}, + {{{ makeGetValue('value', '2', 'float') }}} + ); + break; + case 0x1006 /* AL_VELOCITY */: + AL.currentContext.src[source - 1].panner.setVelocity( + {{{ makeGetValue('value', '0', 'float') }}}, + {{{ makeGetValue('value', '1', 'float') }}}, + {{{ makeGetValue('value', '2', 'float') }}} + ); + break; + default: + console.log("alSourcefv with param " + param + " not implemented yet"); + break; + } + }, + + alSourceQueueBuffers: function(source, count, buffers) { + if (!AL.currentContext) { + console.error("alSourceQueueBuffers called without a valid context"); + return; + } + if (source > AL.currentContext.src.length) { + console.error("alSourceQueueBuffers called with an invalid source"); + return; + } + if (count != 1) { + console.error("Queuing multiple buffers using alSourceQueueBuffers is not supported yet"); + return; + } + for (var i = 0; i < count; ++i) { + var buffer = {{{ makeGetValue('buffers', 'i', 'i32') }}}; + if (buffer > AL.currentContext.buf.length) { + console.error("alSourceQueueBuffers called with an invalid buffer"); + return; + } + AL.currentContext.src[source - 1].buffer = AL.currentContext.buf[buffer - 1].buf; + } + }, + + alSourceUnqueueBuffers: function(source, count, buffers) + { + if (!AL.currentContext) { + console.error("alSourceUnqueueBuffers called without a valid context"); + return; + } + if (source > AL.currentContext.src.length) { + console.error("alSourceUnqueueBuffers called with an invalid source"); + return; + } + if (count != 1) { + console.error("Queuing multiple buffers using alSourceUnqueueBuffers is not supported yet"); + return; + } + for (var i = 0; i < count; ++i) { + var buffer = AL.currentContext.src[source - 1].buffer; + for (var j = 0; j < AL.currentContext.buf.length; ++j) { + if (buffer == AL.currentContext.buf[j].buf) { + {{{ makeSetValue('buffers', 'i', 'j+1', 'i32') }}}; + AL.currentContext.src[source - 1].buffer = null; + break; + } + } + } + }, + + alDeleteBuffers: function(count, buffers) + { + if (!AL.currentContext) { + console.error("alDeleteBuffers called without a valid context"); + return; + } + for (var i = 0; i < count; ++i) { + var bufferIdx = {{{ makeGetValue('buffers', 'i', 'i32') }}} - 1; + var buffer = AL.currentContext.buf[bufferIdx].buf; + for (var j = 0; j < AL.currentContext.src.length; ++j) { + if (buffer == AL.currentContext.src[j].buffer) { + AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */; + return; + } + } + delete AL.currentContext.buf[bufferIdx]; + } + }, + + alGenBuffers: function(count, buffers) { + if (!AL.currentContext) { + console.error("alGenBuffers called without a valid context"); + return; + } + for (var i = 0; i < count; ++i) { + AL.currentContext.buf.push({buf: null}); + {{{ makeSetValue('buffers', 'i', 'AL.currentContext.buf.length', 'i32') }}}; + } + }, + + alBufferData: function(buffer, format, data, size, freq) { + if (!AL.currentContext) { + console.error("alBufferData called without a valid context"); + return; + } + if (buffer > AL.currentContext.buf.length) { + console.error("alBufferData called with an invalid buffer"); + return; + } + var channels, bytes; + switch (format) { + case 0x1100 /* AL_FORMAT_MONO8 */: + bytes = 1; + channels = 1; + break; + case 0x1101 /* AL_FORMAT_MONO16 */: + bytes = 2; + channels = 1; + break; + case 0x1102 /* AL_FORMAT_STEREO8 */: + bytes = 1; + channels = 2; + break; + case 0x1103 /* AL_FORMAT_STEREO16 */: + bytes = 2; + channels = 2; + break; + default: + console.error("alBufferData called with invalid format " + format); + return; + } + AL.currentContext.buf[buffer - 1].buf = AL.currentContext.ctx.createBuffer(channels, size / (bytes * channels), freq); + var buf = new Array(channels); + for (var i = 0; i < channels; ++i) { + buf[i] = AL.currentContext.buf[buffer - 1].buf.getChannelData(i); + } + for (var i = 0; i < size / (bytes * channels); ++i) { + for (var j = 0; j < channels; ++j) { + switch (bytes) { + case 1: + var val = {{{ makeGetValue('data', 'i*channels+j', 'i8') }}}; + buf[j][i] = -1.0 + val * (2/256); + break; + case 2: + var val = {{{ makeGetValue('data', '2*(i*channels+j)', 'i16') }}}; + buf[j][i] = val/32768; + break; + } + } + } + }, + + alSourcePlay__deps: ["alSourceStop"], + alSourcePlay: function(source) { + if (!AL.currentContext) { + console.error("alSourcePlay called without a valid context"); + return; + } + if (source > AL.currentContext.src.length) { + console.error("alSourcePlay called with an invalid source"); + return; + } + var offset = 0; + if ("src" in AL.currentContext.src[source - 1]) { + // If the source is already playing, we need to resume from beginning. + // We do that by stopping the current source and replaying it. + _alSourceStop(source); + } else if (AL.currentContext.src[source - 1].paused) { + // So now we have to resume playback, remember the offset here. + offset = AL.currentContext.src[source - 1].pausedTime - + AL.currentContext.src[source - 1].playTime; + } + var src = AL.currentContext.ctx.createBufferSource(); + src.loop = AL.currentContext.src[source - 1].loop; + src.buffer = AL.currentContext.src[source - 1].buffer; + src.connect(AL.currentContext.src[source - 1].gain); + src.start(0, offset); + // Work around Firefox bug 851338 + AL.currentContext.src[source - 1].playTime = AL.currentContext.ctx.currentTime || 0; + AL.currentContext.src[source - 1].paused = false; + AL.currentContext.src[source - 1]['src'] = src; + }, + + alSourceStop: function(source) { + if (!AL.currentContext) { + console.error("alSourceStop called without a valid context"); + return; + } + if (source > AL.currentContext.src.length) { + console.error("alSourceStop called with an invalid source"); + return; + } + if ("src" in AL.currentContext.src[source - 1]) { + AL.currentContext.src[source - 1]["src"].stop(0); + delete AL.currentContext.src[source - 1]["src"]; + } + }, + + alSourcePause: function(source) { + if (!AL.currentContext) { + console.error("alSourcePause called without a valid context"); + return; + } + if (source > AL.currentContext.src.length) { + console.error("alSourcePause called with an invalid source"); + return; + } + if ("src" in AL.currentContext.src[source - 1] && + !AL.currentContext.src[source - 1].paused) { + AL.currentContext.src[source - 1].paused = true; + // Work around Firefox bug 851338 + AL.currentContext.src[source - 1].pausedTime = AL.currentContext.ctx.currentTime || 0; + AL.currentContext.src[source - 1]["src"].stop(0); + delete AL.currentContext.src[source - 1].src; + } + }, + + alGetSourcei: function(source, param, value) { + if (!AL.currentContext) { + console.error("alGetSourcei called without a valid context"); + return; + } + if (source > AL.currentContext.src.length) { + console.error("alGetSourcei called with an invalid source"); + return; + } + switch (param) { + case 0x202 /* AL_SOURCE_RELATIVE */: + // Always return 1 + {{{ makeSetValue('value', '0', '1', 'i32') }}}; + break; + case 0x1009 /* AL_BUFFER */: + if (AL.currentContext.src[source - 1].buffer == null) { + {{{ makeSetValue('value', '0', '0', 'i32') }}}; + } else { + var buf = AL.currentContext.src[source - 1].buffer; + for (var i = 0; i < AL.currentContext.buf.length; ++i) { + if (buf == AL.currentContext.buf[i].buf) { + {{{ makeSetValue('value', '0', 'i+1', 'i32') }}}; + return; + } + } + {{{ makeSetValue('value', '0', '0', 'i32') }}}; + } + break; + case 0x1010 /* AL_SOURCE_STATE */: + if ("src" in AL.currentContext.src[source - 1]) { + {{{ makeSetValue('value', '0', '0x1012', 'i32') }}} /* AL_PLAYING */; + } else if (AL.currentContext.src[source - 1].paused) { + {{{ makeSetValue('value', '0', '0x1013', 'i32') }}} /* AL_PAUSED */; + } else if (AL.currentContext.src[source - 1].playTime == -1) { + {{{ makeSetValue('value', '0', '0x1011', 'i32') }}} /* AL_INITIAL */; + } else { + {{{ makeSetValue('value', '0', '0x1014', 'i32') }}} /* AL_STOPPED */; + } + break; + case 0x1015 /* AL_BUFFERS_QUEUED */: + if (AL.currentContext.src[source - 1].buffer) { + {{{ makeSetValue('value', '0', '1', 'i32') }}} + } else { + {{{ makeSetValue('value', '0', '0', 'i32') }}} + } + break; + case 0x1016 /* AL_BUFFERS_PROCESSED */: + // Always return 1 + {{{ makeSetValue('value', '0', '1', 'i32') }}} + break; + } + }, + + alDistanceModel: function(model) { + if (model != 0 /* AL_NONE */) { + console.log("Only alDistanceModel(AL_NONE) is currently supported"); + } + }, + + alListenerfv: function(param, values) { + console.log("alListenerfv is not supported yet"); + }, + + alIsExtensionPresent: function(extName) { + return 0; + }, + + alcIsExtensionPresent: function(device, extName) { + return 0; + }, + + alGetProcAddress: function(fname) { + return 0; + }, + + alcGetProcAddress: function(device, fname) { + return 0; + }, + +}; + +autoAddDeps(LibraryOpenAL, '$AL'); +mergeInto(LibraryManager.library, LibraryOpenAL); + diff --git a/src/library_sdl.js b/src/library_sdl.js index 96ae6fa2..9fc979d2 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -35,6 +35,7 @@ var LibrarySDL = { mixerFormat: 0x8010, // AUDIO_S16LSB mixerNumChannels: 2, mixerChunkSize: 1024, + channelMinimumNumber: 0, GL: false, // Set to true if we call SDL_SetVideoMode with SDL_OPENGL, and if so, we do not create 2D canvases&contexts for blitting // Note that images loaded before SDL_SetVideoMode will not get this optimization @@ -661,8 +662,8 @@ var LibrarySDL = { {{{ makeSetValue('ret+Runtime.QUANTUM_SIZE*0', '0', '0', 'i32') }}} // TODO {{{ makeSetValue('ret+Runtime.QUANTUM_SIZE*1', '0', '0', 'i32') }}} // TODO {{{ makeSetValue('ret+Runtime.QUANTUM_SIZE*2', '0', '0', 'void*') }}} - {{{ makeSetValue('ret+Runtime.QUANTUM_SIZE*3', '0', 'SDL.defaults.width', 'i32') }}} - {{{ makeSetValue('ret+Runtime.QUANTUM_SIZE*4', '0', 'SDL.defaults.height', 'i32') }}} + {{{ makeSetValue('ret+Runtime.QUANTUM_SIZE*3', '0', 'Module["canvas"].width', 'i32') }}} + {{{ makeSetValue('ret+Runtime.QUANTUM_SIZE*4', '0', 'Module["canvas"].height', 'i32') }}} return ret; }, @@ -919,7 +920,26 @@ var LibrarySDL = { }, SDL_ShowCursor: function(toggle) { - // TODO + switch (toggle) { + case 0: // SDL_DISABLE + if (Browser.isFullScreen) { // only try to lock the pointer when in full screen mode + Module['canvas'].requestPointerLock(); + return 0; + } else { // else return SDL_ENABLE to indicate the failure + return 1; + } + break; + case 1: // SDL_ENABLE + Module['canvas'].exitPointerLock(); + return 1; + break; + case -1: // SDL_QUERY + return !Browser.pointerLock; + break; + default: + console.log( "SDL_ShowCursor called with unknown toggle parameter value: " + toggle + "." ); + break; + } }, SDL_GetError: function() { @@ -1075,6 +1095,15 @@ var LibrarySDL = { }, SDL_WM_GrabInput: function() {}, + + SDL_WM_ToggleFullScreen: function(surf) { + if (Browser.isFullScreen) { + Module['canvas'].cancelFullScreen(); + return 1; + } else { + return 0; + } + }, // SDL_Image @@ -1289,7 +1318,9 @@ var LibrarySDL = { Mix_FreeChunk: function(id) { SDL.audios[id] = null; }, - + Mix_ReserveChannels: function(num) { + SDL.channelMinimumNumber = num; + }, Mix_PlayChannel: function(channel, id, loops) { // TODO: handle loops @@ -1302,8 +1333,8 @@ var LibrarySDL = { // If the user asks us to allocate a channel automatically, get the first // free one. if (channel == -1) { - channel = 0; - for (var i = 0; i < SDL.numChannels; i++) { + channel = SDL.channelMinimumNumber; + for (var i = SDL.channelMinimumNumber; i < SDL.numChannels; i++) { if (!SDL.channels[i].audio) { channel = i; break; @@ -1375,6 +1406,7 @@ var LibrarySDL = { audio.play(); } audio.volume = channelInfo.volume; + audio.paused = false; return channel; }, Mix_PlayChannelTimed: 'Mix_PlayChannel', // XXX ignore Timing @@ -1463,10 +1495,71 @@ var LibrarySDL = { return (SDL.music.audio && !SDL.music.audio.paused) ? 1 : 0; }, + // http://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer_38.html#SEC38 + // "Note: Does not check if the channel has been paused." + Mix_Playing: function(channel) { + if (channel === -1) { + var count = 0; + for (var i = 0; i < SDL.channels.length; i++) { + count += _Mix_Playing(i); + } + return count; + } + var info = SDL.channels[channel]; + if (info && info.audio && !info.audio.paused) { + return 1; + } + return 0; + }, + + Mix_Pause: function(channel) { + if (channel === -1) { + for (var i = 0; i<SDL.channels.length;i++) { + _Mix_Pause(i); + } + return; + } + var info = SDL.channels[channel]; + if (info && info.audio) { + info.audio.pause(); + info.audio.paused = true; + } + }, + + // http://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer_39.html#SEC39 + Mix_Paused: function(channel) { + if (channel === -1) { + var pausedCount = 0; + for (var i = 0; i<SDL.channels.length;i++) { + pausedCount += _Mix_Paused(i); + } + return pausedCount; + } + var info = SDL.channels[channel]; + if (info && info.audio && info.audio.paused) { + return 1; + } + return 0; + }, + Mix_PausedMusic: function() { return (SDL.music.audio && SDL.music.audio.paused) ? 1 : 0; }, + // http://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer_33.html#SEC33 + Mix_Resume: function(channel) { + if (channel === -1) { + for (var i = 0; i<SDL.channels.length;i++) { + _Mix_Resume(i); + } + return; + } + var info = SDL.channels[channel]; + if (info && info.audio) { + info.audio.play(); + } + }, + // SDL TTF TTF_Init: function() { return 0 }, diff --git a/src/long.js b/src/long.js index c3b0e605..6d0e873d 100644 --- a/src/long.js +++ b/src/long.js @@ -1530,13 +1530,6 @@ var i64Math = (function() { // Emscripten wrapper // Emscripten wrapper var Wrapper = { - add: function(xl, xh, yl, yh) { - var x = new goog.math.Long(xl, xh); - var y = new goog.math.Long(yl, yh); - var ret = x.add(y); - HEAP32[tempDoublePtr>>2] = ret.low_; - HEAP32[tempDoublePtr+4>>2] = ret.high_; - }, subtract: function(xl, xh, yl, yh) { var x = new goog.math.Long(xl, xh); var y = new goog.math.Long(yl, yh); @@ -1551,6 +1544,17 @@ var i64Math = (function() { // Emscripten wrapper HEAP32[tempDoublePtr>>2] = ret.low_; HEAP32[tempDoublePtr+4>>2] = ret.high_; }, + abs: function(l, h) { + var x = new goog.math.Long(l, h); + var ret; + if (x.isNegative()) { + ret = x.negate(); + } else { + ret = x; + } + HEAP32[tempDoublePtr>>2] = ret.low_; + HEAP32[tempDoublePtr+4>>2] = ret.high_; + }, ensureTemps: function() { if (Wrapper.ensuredTemps) return; Wrapper.ensuredTemps = true; diff --git a/src/modules.js b/src/modules.js index afdbc21e..bda8a605 100644 --- a/src/modules.js +++ b/src/modules.js @@ -90,6 +90,7 @@ var Debugging = { lines[i] = ';'; continue; } + if (line[0] == '!') skipLine = true; lines[i] = skipLine ? ';' : line; } @@ -290,7 +291,7 @@ var Functions = { var sig = ASM_JS ? Functions.implementedFunctions[ident] || Functions.unimplementedFunctions[ident] || LibraryManager.library[ident.substr(1) + '__sig'] : 'x'; assert(sig, ident); if (!tables[sig]) tables[sig] = emptyTable(sig); // TODO: make them compact - tables[sig][this.indexedFunctions[ident]] = ident; + tables[sig][this.indexedFunctions[ident]] = ident in DEAD_FUNCTIONS ? '0' : ident; } var generated = false; var wrapped = {}; @@ -314,7 +315,7 @@ var Functions = { } if (ASM_JS) { var curr = table[i]; - if (curr && !Functions.implementedFunctions[curr]) { + if (curr && curr != '0' && !Functions.implementedFunctions[curr]) { // This is a library function, we can't just put it in the function table, need a wrapper if (!wrapped[curr]) { var args = '', arg_coercions = '', call = curr + '(', retPre = '', retPost = ''; @@ -373,7 +374,7 @@ var LibraryManager = { load: function() { if (this.library) return; - var libraries = ['library.js', 'library_browser.js', 'library_sdl.js', 'library_gl.js', 'library_glut.js', 'library_xlib.js', 'library_egl.js', 'library_gc.js', 'library_jansson.js'].concat(additionalLibraries); + var libraries = ['library.js', 'library_browser.js', 'library_sdl.js', 'library_gl.js', 'library_glut.js', 'library_xlib.js', 'library_egl.js', 'library_gc.js', 'library_jansson.js', 'library_openal.js'].concat(additionalLibraries); for (var i = 0; i < libraries.length; i++) { eval(processMacros(preprocess(read(libraries[i])))); } diff --git a/src/parseTools.js b/src/parseTools.js index 7f4f3a18..2664baed 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -172,6 +172,7 @@ function getBits(type) { } if (isStructType(type)) { var typeData = Types.types[type]; + if (typeData === undefined) return 0; return typeData.flatSize*8; } return 0; @@ -583,7 +584,7 @@ function cleanOutTokens(filterOut, tokens, indexes) { if (typeof indexes !== 'object') indexes = [indexes]; for (var i = indexes.length-1; i >=0; i--) { var index = indexes[i]; - while (tokens[index].text in filterOut) { + while (index < tokens.length && tokens[index].text in filterOut) { tokens.splice(index, 1); } } @@ -1248,7 +1249,6 @@ function makeSetValueAsm(ptr, pos, value, type, noNeedFirst, ignore, align, noSa return makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, sep, forcedAlign, true); } -var SEEK_OPTIMAL_ALIGN_MIN = 20; var UNROLL_LOOP_MAX = 8; function makeSetValues(ptr, pos, value, type, num, align) { @@ -1268,7 +1268,7 @@ function makeSetValues(ptr, pos, value, type, num, align) { } else { // USE_TYPED_ARRAYS == 2 // If we don't know how to handle this at compile-time, or handling it is best done in a large amount of code, call memset // TODO: optimize the case of numeric num but non-numeric value - if (!isNumber(num) || !isNumber(value) || (align < 4 && parseInt(num) >= SEEK_OPTIMAL_ALIGN_MIN)) { + if (!isNumber(num) || !isNumber(value) || (parseInt(num)/align >= UNROLL_LOOP_MAX)) { return '_memset(' + asmCoercion(getFastValue(ptr, '+', pos), 'i32') + ', ' + asmCoercion(value, 'i32') + ', ' + asmCoercion(num, 'i32') + ')'; } num = parseInt(num); @@ -1283,13 +1283,7 @@ function makeSetValues(ptr, pos, value, type, num, align) { [4, 2, 1].forEach(function(possibleAlign) { if (num == 0) return; if (align >= possibleAlign) { - if (num <= UNROLL_LOOP_MAX*possibleAlign || ASM_JS) { // XXX test asm performance - ret.push(unroll('i' + (possibleAlign*8), Math.floor(num/possibleAlign), possibleAlign, values[possibleAlign])); - } else { - ret.push('for (var $$dest = ' + getFastValue(ptr, '+', pos) + (possibleAlign > 1 ? '>>' + log2(possibleAlign) : '') + ', ' + - '$$stop = $$dest + ' + Math.floor(num/possibleAlign) + '; $$dest < $$stop; $$dest++) {\n' + - ' HEAP' + (possibleAlign*8) + '[$$dest] = ' + values[possibleAlign] + '\n}'); - } + ret.push(unroll('i' + (possibleAlign*8), Math.floor(num/possibleAlign), possibleAlign, values[possibleAlign])); pos = getFastValue(pos, '+', Math.floor(num/possibleAlign)*possibleAlign); num %= possibleAlign; } @@ -1326,7 +1320,7 @@ function makeCopyValues(dest, src, num, type, modifier, align, sep) { unroll(type, 1) + ' }'; } else { // USE_TYPED_ARRAYS == 2 // If we don't know how to handle this at compile-time, or handling it is best done in a large amount of code, call memset - if (!isNumber(num) || (align < 4 && parseInt(num) >= SEEK_OPTIMAL_ALIGN_MIN)) { + if (!isNumber(num) || (parseInt(num)/align >= UNROLL_LOOP_MAX)) { return '_memcpy(' + dest + ', ' + src + ', ' + num + ')'; } num = parseInt(num); @@ -1334,16 +1328,7 @@ function makeCopyValues(dest, src, num, type, modifier, align, sep) { [4, 2, 1].forEach(function(possibleAlign) { if (num == 0) return; if (align >= possibleAlign) { - // If we can unroll the loop, do so. Also do so if we must unroll it (we do not create real loops when inlined) - if (num <= UNROLL_LOOP_MAX*possibleAlign || sep == ',' || ASM_JS) { // XXX test asm performance - ret.push(unroll('i' + (possibleAlign*8), Math.floor(num/possibleAlign), possibleAlign)); - } else { - assert(sep == ';'); - ret.push('for (var $$src = ' + src + (possibleAlign > 1 ? '>>' + log2(possibleAlign) : '') + ', ' + - '$$dest = ' + dest + (possibleAlign > 1 ? '>>' + log2(possibleAlign) : '') + ', ' + - '$$stop = $$src + ' + Math.floor(num/possibleAlign) + '; $$src < $$stop; $$src++, $$dest++) {\n' + - ' HEAP' + (possibleAlign*8) + '[$$dest] = HEAP' + (possibleAlign*8) + '[$$src]\n}'); - } + ret.push(unroll('i' + (possibleAlign*8), Math.floor(num/possibleAlign), possibleAlign)); src = getFastValue(src, '+', Math.floor(num/possibleAlign)*possibleAlign); dest = getFastValue(dest, '+', Math.floor(num/possibleAlign)*possibleAlign); num %= possibleAlign; @@ -1739,13 +1724,14 @@ function makeLLVMStruct(values) { } } -function makeStructuralReturn(values) { +function makeStructuralReturn(values, inAsm) { if (USE_TYPED_ARRAYS == 2) { - var i = 0; - return 'return (' + values.slice(1).map(function(value) { - return ASM_JS ? 'asm.setTempRet' + (i++) + '(' + value + ')' + var i = -1; + return 'return ' + asmCoercion(values.slice(1).map(function(value) { + i++; + return ASM_JS ? (inAsm ? 'tempRet' + i + ' = ' + value : 'asm.setTempRet' + i + '(' + value + ')') : 'tempRet' + (i++) + ' = ' + value; - }).concat([values[0]]).join(',') + ')'; + }).concat([values[0]]).join(','), 'i32'); } else { var i = 0; return 'return { ' + values.map(function(value) { @@ -1986,6 +1972,10 @@ function processMathop(item) { return finish(['(i64Math' + (ASM_JS ? '_' : '.') + type + '(' + asmCoercion(low1, 'i32') + ',' + asmCoercion(high1, 'i32') + ',' + asmCoercion(low2, 'i32') + ',' + asmCoercion(high2, 'i32') + (lastArg ? ',' + asmCoercion(+lastArg, 'i32') : '') + '),' + makeGetValue('tempDoublePtr', 0, 'i32') + ')', makeGetValue('tempDoublePtr', Runtime.getNativeTypeSize('i32'), 'i32')]); } + function i64PreciseLib(type) { + Types.preciseI64MathUsed = true; + return finish(['_i64' + type[0].toUpperCase() + type.substr(1) + '(' + low1 + ',' + high1 + ',' + low2 + ',' + high2 + ')', 'tempRet0']); + } switch (op) { // basic integer ops case 'or': { @@ -2000,43 +1990,7 @@ function processMathop(item) { case 'shl': case 'ashr': case 'lshr': { - if (!isNumber(idents[1])) { - return '(Runtime' + (ASM_JS ? '_' : '.') + 'bitshift64(' + idents[0] + '[0], ' + idents[0] + '[1],' + Runtime['BITSHIFT64_' + op.toUpperCase()] + ',' + stripCorrections(idents[1]) + '[0]|0),' + - '[' + makeGetTempDouble(0, 'i32') + ',' + makeGetTempDouble(1, 'i32') + '])'; - } - bits = parseInt(idents[1]); - var ander = Math.pow(2, bits)-1; - if (bits < 32) { - switch (op) { - case 'shl': - return '[' + idents[0] + '[0] << ' + idents[1] + ', ' + - '('+idents[0] + '[1] << ' + idents[1] + ') | ((' + idents[0] + '[0]&(' + ander + '<<' + (32 - bits) + ')) >>> (32-' + idents[1] + '))]'; - case 'ashr': - return '[((('+idents[0] + '[0] >>> ' + idents[1] + ') | ((' + idents[0] + '[1]&' + ander + ')<<' + (32 - bits) + ')) >> 0) >>> 0,' + - '(' + idents[0] + '[1] >> ' + idents[1] + ') >>> 0]'; - case 'lshr': - return '[(('+idents[0] + '[0] >>> ' + idents[1] + ') | ((' + idents[0] + '[1]&' + ander + ')<<' + (32 - bits) + ')) >>> 0,' + - idents[0] + '[1] >>> ' + idents[1] + ']'; - } - } else if (bits == 32) { - switch (op) { - case 'shl': - return '[0, ' + idents[0] + '[0]]'; - case 'ashr': - return '[' + idents[0] + '[1], (' + idents[0] + '[1]|0) < 0 ? ' + ander + ' : 0]'; - case 'lshr': - return '[' + idents[0] + '[1], 0]'; - } - } else { // bits > 32 - switch (op) { - case 'shl': - return '[0, ' + idents[0] + '[0] << ' + (bits - 32) + ']'; - case 'ashr': - return '[(' + idents[0] + '[1] >> ' + (bits - 32) + ') >>> 0, (' + idents[0] + '[1]|0) < 0 ? ' + ander + ' : 0]'; - case 'lshr': - return '[' + idents[0] + '[1] >>> ' + (bits - 32) + ', 0]'; - } - } + throw 'shifts should have been legalized!'; } case 'uitofp': case 'sitofp': return RuntimeGenerator.makeBigInt(low1, high1, op[0] == 'u'); case 'fptoui': case 'fptosi': return finish(splitI64(idents[0], true)); @@ -2074,7 +2028,7 @@ function processMathop(item) { // Dangerous, rounded operations. TODO: Fully emulate case 'add': { if (PRECISE_I64_MATH) { - return i64PreciseOp('add'); + return i64PreciseLib('add'); } else { warnI64_1(); return finish(splitI64(mergeI64(idents[0]) + '+' + mergeI64(idents[1]), true)); @@ -2365,3 +2319,7 @@ function getImplementationType(varInfo) { return varInfo.type; } +function charCode(char) { + return char.charCodeAt(0); +} + diff --git a/src/preamble.js b/src/preamble.js index 9bc68d8f..6f3a969c 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -503,6 +503,7 @@ function allocate(slab, types, allocator, ptr) { Module['allocate'] = allocate; function Pointer_stringify(ptr, /* optional */ length) { +#if UTF_STRING_SUPPORT var utf8 = new Runtime.UTF8Processor(); var nullTerminated = typeof(length) == "undefined"; var ret = ""; @@ -519,18 +520,16 @@ function Pointer_stringify(ptr, /* optional */ length) { if (!nullTerminated && i == length) break; } return ret; +#else +#if USE_TYPED_ARRAYS == 2 + return String.fromCharCode.apply(String, HEAPU8.subarray(ptr, ptr + (length || _strlen(ptr)))); +#else + throw 'unsupported combination'; +#endif +#endif } Module['Pointer_stringify'] = Pointer_stringify; -function Array_stringify(array) { - var ret = ""; - for (var i = 0; i < array.length; i++) { - ret += String.fromCharCode(array[i]); - } - return ret; -} -Module['Array_stringify'] = Array_stringify; - // Memory management var PAGE_SIZE = 4096; @@ -848,5 +847,22 @@ Module['removeRunDependency'] = removeRunDependency; Module["preloadedImages"] = {}; // maps url to image data Module["preloadedAudios"] = {}; // maps url to audio data +#if PGO +var PGOMonitor = { + called: {}, + dump: function() { + var dead = []; + for (var i = 0; i < this.allGenerated.length; i++) { + var func = this.allGenerated[i]; + if (!this.called[func]) dead.push(func); + } + Module.print('-s DEAD_FUNCTIONS=\'' + JSON.stringify(dead) + '\'\n'); + } +}; +__ATEXIT__.push({ func: function() { PGOMonitor.dump() } }); +if (!Module.preRun) Module.preRun = []; +Module.preRun.push(function() { addRunDependency('pgo') }); +#endif + // === Body === diff --git a/src/relooper/Relooper.cpp b/src/relooper/Relooper.cpp index 1a7acc15..ae393de3 100644 --- a/src/relooper/Relooper.cpp +++ b/src/relooper/Relooper.cpp @@ -347,17 +347,25 @@ struct RelooperRecursor { RelooperRecursor(Relooper *ParentInit) : Parent(ParentInit) {} }; +typedef std::list<Block*> BlockList; + void Relooper::Calculate(Block *Entry) { // Scan and optimize the input struct PreOptimizer : public RelooperRecursor { PreOptimizer(Relooper *Parent) : RelooperRecursor(Parent) {} BlockSet Live; - void FindLive(Block *Curr) { - if (Live.find(Curr) != Live.end()) return; - Live.insert(Curr); - for (BlockBranchMap::iterator iter = Curr->BranchesOut.begin(); iter != Curr->BranchesOut.end(); iter++) { - FindLive(iter->first); + void FindLive(Block *Root) { + BlockList ToInvestigate; + ToInvestigate.push_back(Root); + while (ToInvestigate.size() > 0) { + Block *Curr = ToInvestigate.front(); + ToInvestigate.pop_front(); + if (Live.find(Curr) != Live.end()) continue; + Live.insert(Curr); + for (BlockBranchMap::iterator iter = Curr->BranchesOut.begin(); iter != Curr->BranchesOut.end(); iter++) { + ToInvestigate.push_back(iter->first); + } } } @@ -529,7 +537,6 @@ void Relooper::Calculate(Block *Entry) { // ignore directly reaching the entry itself by another entry. void FindIndependentGroups(BlockSet &Blocks, BlockSet &Entries, BlockBlockSetMap& IndependentGroups) { typedef std::map<Block*, Block*> BlockBlockMap; - typedef std::list<Block*> BlockList; struct HelperClass { BlockBlockSetMap& IndependentGroups; @@ -872,33 +879,38 @@ void Relooper::Calculate(Block *Entry) { // A flow operation is trivially unneeded if the shape we naturally get to by normal code // execution is the same as the flow forces us to. void RemoveUnneededFlows(Shape *Root, Shape *Natural=NULL) { - SHAPE_SWITCH(Root, { - // If there is a next block, we already know at Simple creation time to make direct branches, - // and we can do nothing more. If there is no next however, then Natural is where we will - // go to by doing nothing, so we can potentially optimize some branches to direct. - if (Simple->Next) { - RemoveUnneededFlows(Simple->Next, Natural); - } else { - for (BlockBranchMap::iterator iter = Simple->Inner->ProcessedBranchesOut.begin(); iter != Simple->Inner->ProcessedBranchesOut.end(); iter++) { - Block *Target = iter->first; - Branch *Details = iter->second; - if (Details->Type != Branch::Direct && Target->Parent == Natural) { - Details->Type = Branch::Direct; - if (MultipleShape *Multiple = Shape::IsMultiple(Details->Ancestor)) { - Multiple->NeedLoop--; + Shape *Next = Root; + while (Next) { + Root = Next; + Next = NULL; + SHAPE_SWITCH(Root, { + // If there is a next block, we already know at Simple creation time to make direct branches, + // and we can do nothing more. If there is no next however, then Natural is where we will + // go to by doing nothing, so we can potentially optimize some branches to direct. + if (Simple->Next) { + Next = Simple->Next; + } else { + for (BlockBranchMap::iterator iter = Simple->Inner->ProcessedBranchesOut.begin(); iter != Simple->Inner->ProcessedBranchesOut.end(); iter++) { + Block *Target = iter->first; + Branch *Details = iter->second; + if (Details->Type != Branch::Direct && Target->Parent == Natural) { + Details->Type = Branch::Direct; + if (MultipleShape *Multiple = Shape::IsMultiple(Details->Ancestor)) { + Multiple->NeedLoop--; + } } } } - } - }, { - for (BlockShapeMap::iterator iter = Multiple->InnerMap.begin(); iter != Multiple->InnerMap.end(); iter++) { - RemoveUnneededFlows(iter->second, Multiple->Next); - } - RemoveUnneededFlows(Multiple->Next, Natural); - }, { - RemoveUnneededFlows(Loop->Inner, Loop->Inner); - RemoveUnneededFlows(Loop->Next, Natural); - }); + }, { + for (BlockShapeMap::iterator iter = Multiple->InnerMap.begin(); iter != Multiple->InnerMap.end(); iter++) { + RemoveUnneededFlows(iter->second, Multiple->Next); + } + Next = Multiple->Next; + }, { + RemoveUnneededFlows(Loop->Inner, Loop->Inner); + Next = Loop->Next; + }); + } } // After we know which loops exist, we can calculate which need to be labeled diff --git a/src/relooper/fuzzer.py b/src/relooper/fuzzer.py index 887eab3b..96929028 100644 --- a/src/relooper/fuzzer.py +++ b/src/relooper/fuzzer.py @@ -98,14 +98,14 @@ int main() { open('fuzz.slow.js', 'w').write(slow) open('fuzz.cpp', 'w').write(fast) print '_' - slow_out = subprocess.Popen(['/home/alon/Dev/mozilla-central/js/src/fast/js', '-m', '-n', 'fuzz.slow.js'], stdout=subprocess.PIPE).communicate()[0] + slow_out = subprocess.Popen(['/home/alon/Dev/odinmonkey/js/src/fast/js', '-m', '-n', 'fuzz.slow.js'], stdout=subprocess.PIPE).communicate()[0] print '.' subprocess.call(['g++', 'fuzz.cpp', 'Relooper.o', '-o', 'fuzz', '-g']) print '*' subprocess.call(['./fuzz'], stdout=open('fuzz.fast.js', 'w')) print '-' - fast_out = subprocess.Popen(['/home/alon/Dev/mozilla-central/js/src/fast/js', '-m', '-n', 'fuzz.fast.js'], stdout=subprocess.PIPE).communicate()[0] + fast_out = subprocess.Popen(['/home/alon/Dev/odinmonkey/js/src/fast/js', '-m', '-n', 'fuzz.fast.js'], stdout=subprocess.PIPE).communicate()[0] print if slow_out != fast_out: diff --git a/src/relooper/test.txt b/src/relooper/test.txt index b7c8794d..12d0ef39 100644 --- a/src/relooper/test.txt +++ b/src/relooper/test.txt @@ -54,7 +54,7 @@ while(1) { // code 2 if (!($2)) { var $x_1 = $x_0; - label = 19; + label = 18; break; } // code 3 @@ -64,7 +64,7 @@ while(1) { var $i_0 = $7;var $x_0 = $5; } } -if (label == 19) { +if (label == 18) { // code 7 } // code 4 diff --git a/src/runtime.js b/src/runtime.js index 7f97da35..2a26db28 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -27,7 +27,7 @@ var RuntimeGenerator = { // The stack is always QUANTUM SIZE aligned, so we may not need to force alignment here var ret = RuntimeGenerator.alloc(size, 'STACK', false, sep, USE_TYPED_ARRAYS != 2 || (isNumber(size) && parseInt(size) % {{{ QUANTUM_SIZE }}} == 0)); if (ASSERTIONS) { - ret += sep + 'assert(STACKTOP|0 < STACK_MAX|0)'; + ret += sep + 'assert(' + asmCoercion('(STACKTOP|0) < (STACK_MAX|0)', 'i32') + ')'; } return ret; }, @@ -38,12 +38,12 @@ var RuntimeGenerator = { if (initial > 0) ret += '; STACKTOP = (STACKTOP + ' + initial + ')|0'; if (USE_TYPED_ARRAYS == 2) { assert(initial % QUANTUM_SIZE == 0); - if (ASSERTIONS) { - ret += '; assert(STACKTOP|0 % {{{ QUANTUM_SIZE }}} == 0)'; + if (ASSERTIONS && QUANTUM_SIZE == 4) { + ret += '; assert(' + asmCoercion('!(STACKTOP&3)', 'i32') + ')'; } } if (ASSERTIONS) { - ret += '; assert(STACKTOP < STACK_MAX)'; + ret += '; assert(' + asmCoercion('(STACKTOP|0) < (STACK_MAX|0)', 'i32') + ')'; } if (false) { ret += '; _memset(' + asmCoercion('__stackBase__', 'i32') + ', 0, ' + initial + ')'; @@ -87,7 +87,7 @@ var RuntimeGenerator = { }; function unInline(name_, params) { - var src = '(function ' + name_ + '(' + params + ') { var ret = ' + RuntimeGenerator[name_].apply(null, params) + '; return ret; })'; + var src = '(function(' + params + ') { var ret = ' + RuntimeGenerator[name_].apply(null, params) + '; return ret; })'; var ret = eval(src); return ret; } @@ -122,57 +122,6 @@ var Runtime = { INT_TYPES: set('i1', 'i8', 'i16', 'i32', 'i64'), FLOAT_TYPES: set('float', 'double'), - // Mirrors processMathop's treatment of constants (which we optimize directly) - BITSHIFT64_SHL: 0, - BITSHIFT64_ASHR: 1, - BITSHIFT64_LSHR: 2, - bitshift64: function(low, high, op, bits) { - var ret; - var ander = Math.pow(2, bits)-1; - if (bits < 32) { - switch (op) { - case Runtime.BITSHIFT64_SHL: - ret = [low << bits, (high << bits) | ((low&(ander << (32 - bits))) >>> (32 - bits))]; - break; - case Runtime.BITSHIFT64_ASHR: - ret = [(((low >>> bits ) | ((high&ander) << (32 - bits))) >> 0) >>> 0, (high >> bits) >>> 0]; - break; - case Runtime.BITSHIFT64_LSHR: - ret = [((low >>> bits) | ((high&ander) << (32 - bits))) >>> 0, high >>> bits]; - break; - } - } else if (bits == 32) { - switch (op) { - case Runtime.BITSHIFT64_SHL: - ret = [0, low]; - break; - case Runtime.BITSHIFT64_ASHR: - ret = [high, (high|0) < 0 ? ander : 0]; - break; - case Runtime.BITSHIFT64_LSHR: - ret = [high, 0]; - break; - } - } else { // bits > 32 - switch (op) { - case Runtime.BITSHIFT64_SHL: - ret = [0, low << (bits - 32)]; - break; - case Runtime.BITSHIFT64_ASHR: - ret = [(high >> (bits - 32)) >>> 0, (high|0) < 0 ? ander : 0]; - break; - case Runtime.BITSHIFT64_LSHR: - ret = [high >>> (bits - 32) , 0]; - break; - } - } -#if ASSERTIONS - assert(ret); -#endif - HEAP32[tempDoublePtr>>2] = ret[0]; // cannot use utility functions since we are in runtime itself - HEAP32[tempDoublePtr+4>>2] = ret[1]; - }, - // Imprecise bitops utilities or64: function(x, y) { var l = (x | 0) | (y | 0); @@ -365,6 +314,11 @@ var Runtime = { return ret; }, + removeFunction: function(index) { + var table = FUNCTION_TABLE; // TODO: support asm + table[index] = null; + }, + warnOnce: function(text) { if (!Runtime.warnOnce.shown) Runtime.warnOnce.shown = {}; if (!Runtime.warnOnce.shown[text]) { diff --git a/src/settings.js b/src/settings.js index 1bfcf92a..36f53c3c 100644 --- a/src/settings.js +++ b/src/settings.js @@ -15,7 +15,7 @@ var QUANTUM_SIZE = 4; // This is the size of an individual field in a structure. // the normal value of 4 means all fields take 4 memory addresses, // as per the norm on a 32-bit machine. // - // 1 is somewhat faster than 4, but dangerous. + // Changing this from the default of 4 is deprecated. var CORRECT_SIGNS = 1; // Whether we make sure to convert unsigned values to signed values. // Decreases performance with additional runtime checks. Might not be @@ -63,7 +63,7 @@ var RELOOPER = 'relooper.js'; // Loads the relooper from this path relative to c var USE_TYPED_ARRAYS = 2; // Use typed arrays for the heap. See https://github.com/kripken/emscripten/wiki/Code-Generation-Modes/ // 0 means no typed arrays are used. // 1 has two heaps, IHEAP (int32) and FHEAP (double), - // and addresses there are a match for normal addresses. + // and addresses there are a match for normal addresses. This is deprecated. // 2 is a single heap, accessible through views as int8, int32, etc. This is // the recommended mode both for performance and for compatibility. var USE_FHEAP = 1; // Relevant in USE_TYPED_ARRAYS == 1. If this is disabled, only IHEAP will be used, and FHEAP @@ -171,6 +171,8 @@ var GL_UNSAFE_OPTS = 1; // Enables some potentially-unsafe optimizations in GL e var FULL_ES2 = 0; // Forces support for all GLES2 features, not just the WebGL-friendly subset. var FORCE_GL_EMULATION = 0; // Forces inclusion of full GL emulation code. +var UTF_STRING_SUPPORT = 1; // Perform utf-8 conversion between C and JS strings (adds overhead in such conversions) + var DISABLE_EXCEPTION_CATCHING = 0; // Disables generating code to actually catch exceptions. If the code you // are compiling does not actually rely on catching exceptions (but the // compiler generates code for it, maybe because of stdlibc++ stuff), @@ -230,13 +232,14 @@ var EXPORTED_FUNCTIONS = ['_main']; // Functions that are explicitly exported. T // the generated code even after running closure compiler (on "Module"). // Note the necessary prefix of "_". -var DEFAULT_LIBRARY_FUNCS_TO_INCLUDE = ['memcpy', 'memset', 'malloc', 'free', '$Browser']; // JS library functions (C functions implemented in JS) - // that we include by default. If you want to make sure - // something is included by the JS compiler, add it here. - // For example, if you do not use some emscripten_* - // C API call from C, but you want to call it from JS, - // add it here (and in EXPORTED FUNCTIONS with prefix - // "_", for closure). +// JS library functions (C functions implemented in JS) +// that we include by default. If you want to make sure +// something is included by the JS compiler, add it here. +// For example, if you do not use some emscripten_* +// C API call from C, but you want to call it from JS, +// add it here (and in EXPORTED FUNCTIONS with prefix +// "_", for closure). +var DEFAULT_LIBRARY_FUNCS_TO_INCLUDE = ['memcpy', 'memset', 'malloc', 'free', 'strlen', '$Browser']; var LIBRARY_DEPS_TO_AUTOEXPORT = ['memcpy']; // This list is also used to determine // auto-exporting of library dependencies (i.e., functions that @@ -332,6 +335,14 @@ var ASM_JS = 0; // If 1, generate code in asm.js format. XXX This is highly expe // and will not work on most codebases yet. It is NOT recommended that you // try this yet. +var PGO = 0; // Enables profile-guided optimization in the form of runtime checks for + // which functions are actually called. Emits a list during shutdown that you + // can pass to DEAD_FUNCTIONS (you can also emit the list manually by + // calling PGOMonitor.dump()); +var DEAD_FUNCTIONS = []; // A list of functions that no code will be emitted for, and + // a runtime abort will happen if they are called + // TODO: options to lazily load such functions + var EXPLICIT_ZEXT = 0; // If 1, generate an explicit conversion of zext i1 to i32, using ?: var NECESSARY_BLOCKADDRS = []; // List of (function, block) for all block addresses that are taken. diff --git a/src/shell.html b/src/shell.html index 4f39b26a..8743d403 100644 --- a/src/shell.html +++ b/src/shell.html @@ -19,7 +19,14 @@ </div> <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas> <hr/> - <div class="emscripten"><input type="button" value="fullscreen" onclick="Module.requestFullScreen()"></div> + <div class="emscripten"> + <input type="checkbox" id="resize">Resize canvas + <input type="checkbox" id="pointerLock" checked>Lock/hide mouse pointer + + <input type="button" value="Fullscreen" onclick="Module.requestFullScreen(document.getElementById('pointerLock').checked, + document.getElementById('resize').checked)"> + </div> + <hr/> <textarea class="emscripten" id="output" rows="8"></textarea> <hr> diff --git a/src/utility.js b/src/utility.js index 8db37c61..19444675 100644 --- a/src/utility.js +++ b/src/utility.js @@ -267,6 +267,15 @@ function set() { } var unset = keys; +function numberedSet() { + var args = typeof arguments[0] === 'object' ? arguments[0] : arguments; + var ret = {}; + for (var i = 0; i < args.length; i++) { + ret[args[i]] = i; + } + return ret; +} + function setSub(x, y) { var ret = set(keys(x)); for (yy in y) { |