diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/analyzer.js | 29 | ||||
-rw-r--r-- | src/compiler.js | 15 | ||||
-rw-r--r-- | src/corruptionCheck.js | 98 | ||||
-rw-r--r-- | src/experimental/allow_loopvars_from_memsetcpy_inasm.diff | 97 | ||||
-rw-r--r-- | src/intertyper.js | 4 | ||||
-rw-r--r-- | src/jsifier.js | 63 | ||||
-rw-r--r-- | src/library.js | 481 | ||||
-rw-r--r-- | src/library_browser.js | 93 | ||||
-rw-r--r-- | src/library_egl.js | 41 | ||||
-rw-r--r-- | src/library_gc.js | 2 | ||||
-rw-r--r-- | src/library_gl.js | 761 | ||||
-rw-r--r-- | src/library_glut.js | 6 | ||||
-rw-r--r-- | src/library_sdl.js | 143 | ||||
-rw-r--r-- | src/long.js | 11 | ||||
-rw-r--r-- | src/modules.js | 22 | ||||
-rw-r--r-- | src/parseTools.js | 128 | ||||
-rw-r--r-- | src/preamble.js | 221 | ||||
-rw-r--r-- | src/relooper/Relooper.cpp | 156 | ||||
-rw-r--r-- | src/relooper/fuzzer.py | 4 | ||||
-rw-r--r-- | src/relooper/test2.txt | 15 | ||||
-rw-r--r-- | src/relooper/test3.txt | 38 | ||||
-rw-r--r-- | src/relooper/test4.txt | 21 | ||||
-rw-r--r-- | src/relooper/test6.txt | 15 | ||||
-rw-r--r-- | src/relooper/test_debug.txt | 15 | ||||
-rw-r--r-- | src/relooper/test_fuzz1.txt | 13 | ||||
-rw-r--r-- | src/relooper/test_fuzz5.txt | 27 | ||||
-rw-r--r-- | src/relooper/test_inf.txt | 651 | ||||
-rw-r--r-- | src/runtime.js | 40 | ||||
-rw-r--r-- | src/settings.js | 57 | ||||
-rw-r--r-- | src/shell.html | 11 |
30 files changed, 2088 insertions, 1190 deletions
diff --git a/src/analyzer.js b/src/analyzer.js index 1c53b76c..209e3140 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -18,6 +18,7 @@ function recomputeLines(func) { // Handy sets var BRANCH_INVOKE = set('branch', 'invoke'); +var LABEL_ENDERS = set('branch', 'return'); var SIDE_EFFECT_CAUSERS = set('call', 'invoke', 'atomic'); var UNUNFOLDABLE = set('value', 'structvalue', 'type', 'phiparam'); @@ -88,7 +89,7 @@ function analyzer(data, sidePass) { // Internal line if (!currLabelFinished) { item.functions.slice(-1)[0].labels.slice(-1)[0].lines.push(subItem); // If this line fails, perhaps missing a label? - if (subItem.intertype === 'branch') { + if (subItem.intertype in LABEL_ENDERS) { currLabelFinished = true; } } else { @@ -121,7 +122,8 @@ function analyzer(data, sidePass) { // Legalization if (USE_TYPED_ARRAYS == 2) { function getLegalVars(base, bits, allowLegal) { - if (allowLegal && bits <= 32) return [{ ident: base, bits: bits }]; + bits = bits || 32; // things like pointers are all i32, but show up as 0 bits from getBits + if (allowLegal && bits <= 32) return [{ ident: base + ('i' + bits in Runtime.INT_TYPES ? '' : '$0'), bits: bits }]; if (isNumber(base)) return getLegalLiterals(base, bits); var ret = new Array(Math.ceil(bits/32)); var i = 0; @@ -223,6 +225,9 @@ function analyzer(data, sidePass) { for (var i = 0; i < item.params.length; i++) { if (item.params[i].type == 'i64') item.params[i].type = 'i32'; } + } else if (item.intertype == 'inttoptr') { + var input = item.params[0]; + if (input.type == 'i64') input.type = 'i32'; // inttoptr can only care about 32 bits anyhow since pointers are 32-bit } if (isIllegalType(item.valueType) || isIllegalType(item.type)) { isIllegal = true; @@ -285,7 +290,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, @@ -396,7 +401,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, @@ -643,13 +648,7 @@ function analyzer(data, sidePass) { default: throw 'Invalid mathop for legalization: ' + [value.op, item.lineNum, dump(item)]; } // Do the legalization - var sourceElements; - if (sourceBits <= 32) { - // The input is a legal type - sourceElements = [{ ident: value.params[0].ident, bits: sourceBits }]; - } else { - sourceElements = getLegalVars(value.params[0].ident, sourceBits); - } + var sourceElements = getLegalVars(value.params[0].ident, sourceBits, true); 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'); @@ -681,9 +680,9 @@ function analyzer(data, sidePass) { params: [(signed && j + whole > sourceElements.length) ? signedKeepAlive : null], type: 'i32', }; - if (j == 0 && isUnsignedOp(value.op) && sourceBits < 32) { + if (j == 0 && sourceBits < 32) { // zext sign correction - result.ident = makeSignOp(result.ident, 'i' + sourceBits, 'un', 1, 1); + result.ident = makeSignOp(result.ident, 'i' + sourceBits, isUnsignedOp(value.op) ? 'un' : 're', 1, 1); } if (fraction != 0) { var other = { @@ -953,6 +952,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, @@ -966,6 +966,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, @@ -1381,7 +1382,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 25c306cf..3047daf1 100644 --- a/src/compiler.js +++ b/src/compiler.js @@ -160,12 +160,6 @@ if (SAFE_HEAP >= 2) { SAFE_HEAP_LINES = set(SAFE_HEAP_LINES); // for fast checking } -if (PGO) { // by default, correct everything during PGO - CORRECT_SIGNS = CORRECT_SIGNS || 1; - CORRECT_OVERFLOWS = CORRECT_OVERFLOWS || 1; - CORRECT_ROUNDINGS = CORRECT_ROUNDINGS || 1; -} - EXPORTED_FUNCTIONS = set(EXPORTED_FUNCTIONS); EXPORTED_GLOBALS = set(EXPORTED_GLOBALS); EXCEPTION_CATCHING_WHITELIST = set(EXCEPTION_CATCHING_WHITELIST); @@ -178,20 +172,21 @@ assert(!(USE_TYPED_ARRAYS === 2 && QUANTUM_SIZE !== 4), 'For USE_TYPED_ARRAYS == if (ASM_JS) { assert(!ALLOW_MEMORY_GROWTH, 'Cannot grow asm.js heap'); assert((TOTAL_MEMORY&(TOTAL_MEMORY-1)) == 0, 'asm.js heap must be power of 2'); + assert(DISABLE_EXCEPTION_CATCHING == 1, 'asm.js does not support C++ exceptions yet'); } assert(!(!NAMED_GLOBALS && BUILD_AS_SHARED_LIB)); // shared libraries must have named globals // Output some info and warnings based on settings if (phase == 'pre') { - if (!MICRO_OPTS || !RELOOP || ASSERTIONS || CHECK_SIGNS || CHECK_OVERFLOWS || INIT_STACK || INIT_HEAP || - !SKIP_STACK_IN_SMALL || SAFE_HEAP || PGO || PROFILE || !DISABLE_EXCEPTION_CATCHING) { + if (!MICRO_OPTS || !RELOOP || ASSERTIONS || CHECK_SIGNS || CHECK_OVERFLOWS || INIT_HEAP || + !SKIP_STACK_IN_SMALL || SAFE_HEAP || !DISABLE_EXCEPTION_CATCHING) { print('// Note: Some Emscripten settings will significantly limit the speed of the generated code.'); } else { print('// Note: For maximum-speed code, see "Optimizing Code" on the Emscripten wiki, https://github.com/kripken/emscripten/wiki/Optimizing-Code'); } - if (DOUBLE_MODE || CORRECT_SIGNS || CORRECT_OVERFLOWS || CORRECT_ROUNDINGS) { + if (DOUBLE_MODE || CORRECT_SIGNS || CORRECT_OVERFLOWS || CORRECT_ROUNDINGS || CHECK_HEAP_ALIGN) { print('// Note: Some Emscripten settings may limit the speed of the generated code.'); } } @@ -204,7 +199,7 @@ load('parseTools.js'); load('intertyper.js'); load('analyzer.js'); load('jsifier.js'); -if (RELOOP) load('relooper.js') +if (RELOOP) load(RELOOPER) globalEval(processMacros(preprocess(read('runtime.js')))); Runtime.QUANTUM_SIZE = QUANTUM_SIZE; diff --git a/src/corruptionCheck.js b/src/corruptionCheck.js new file mode 100644 index 00000000..315f5cf0 --- /dev/null +++ b/src/corruptionCheck.js @@ -0,0 +1,98 @@ + +// See settings.js, CORRUPTION_CHECK + +var CorruptionChecker = { + BUFFER_FACTOR: Math.round({{{ CORRUPTION_CHECK }}}), + + ptrs: {}, + checks: 0, + checkFrequency: 1, + + init: function() { + this.realMalloc = _malloc; + _malloc = Module['_malloc'] = this.malloc; + + this.realFree = _free; + _free = Module['_free'] = this.free; + + if (typeof _realloc != 'undefined') { + this.realRealloc = _realloc; + _realloc = Module['_realloc'] = this.realloc; + } + + __ATEXIT__.push({ func: function() { + Module.printErr('No corruption detected, ran ' + CorruptionChecker.checks + ' checks.'); + } }); + }, + malloc: function(size) { + if (size <= 0) size = 1; // malloc(0) sometimes happens - just allocate a larger area, no harm + CorruptionChecker.checkAll(); + size = (size+7)&(~7); + var allocation = CorruptionChecker.realMalloc(size*(1+2*CorruptionChecker.BUFFER_FACTOR)); + var ptr = allocation + size*CorruptionChecker.BUFFER_FACTOR; + assert(!CorruptionChecker.ptrs[ptr]); + CorruptionChecker.ptrs[ptr] = size; + CorruptionChecker.fillBuffer(allocation, size*CorruptionChecker.BUFFER_FACTOR); + CorruptionChecker.fillBuffer(allocation + size*(1+CorruptionChecker.BUFFER_FACTOR), size*CorruptionChecker.BUFFER_FACTOR); + //Module.printErr('malloc ' + size + ' ==> ' + [ptr, allocation]); + return ptr; + }, + free: function(ptr) { + if (!ptr) return; // ok to free(NULL), does nothing + CorruptionChecker.checkAll(); + var size = CorruptionChecker.ptrs[ptr]; + //Module.printErr('free ' + ptr + ' of size ' + size); + assert(size); + var allocation = ptr - size*CorruptionChecker.BUFFER_FACTOR; + //Module.printErr('free ' + ptr + ' of size ' + size + ' and allocation ' + allocation); + delete CorruptionChecker.ptrs[ptr]; + CorruptionChecker.realFree(allocation); + }, + realloc: function(ptr, newSize) { + //Module.printErr('realloc ' + ptr + ' to size ' + newSize); + if (newSize <= 0) newSize = 1; // like in malloc + if (!ptr) return CorruptionChecker.malloc(newSize); // realloc(NULL, size) forwards to malloc according to the spec + var size = CorruptionChecker.ptrs[ptr]; + assert(size); + var allocation = ptr - size*CorruptionChecker.BUFFER_FACTOR; + var newPtr = CorruptionChecker.malloc(newSize); + //Module.printErr('realloc ' + ptr + ' to size ' + newSize + ' is now ' + newPtr); + var newAllocation = newPtr + newSize*CorruptionChecker.BUFFER_FACTOR; + HEAPU8.set(HEAPU8.subarray(ptr, ptr + Math.min(size, newSize)), newPtr); + CorruptionChecker.free(ptr); + return newPtr; + }, + canary: function(x) { + return (x&127) + 10; + }, + fillBuffer: function(buffer, size) { + for (var x = buffer; x < buffer + size; x++) { + {{{ makeSetValue('x', 0, 'CorruptionChecker.canary(x)', 'i8') }}}; + } + }, + checkBuffer: function(buffer, size) { + for (var x = buffer; x < buffer + size; x++) { + if (({{{ makeGetValue('x', 0, 'i8') }}}&255) != CorruptionChecker.canary(x)) { + assert(0, 'Heap corruption detected!' + [x, buffer, size, {{{ makeGetValue('x', 0, 'i8') }}}&255, CorruptionChecker.canary(x)]); + } + } + }, + checkPtr: function(ptr) { + var size = CorruptionChecker.ptrs[ptr]; + assert(size); + var allocation = ptr - size*CorruptionChecker.BUFFER_FACTOR; + CorruptionChecker.checkBuffer(allocation, size*CorruptionChecker.BUFFER_FACTOR); + CorruptionChecker.checkBuffer(allocation + size*(1+CorruptionChecker.BUFFER_FACTOR), size*CorruptionChecker.BUFFER_FACTOR); + }, + checkAll: function(force) { + CorruptionChecker.checks++; + if (!force && CorruptionChecker.checks % CorruptionChecker.checkFrequency != 0) return; + //Module.printErr('checking for corruption ' + (CorruptionChecker.checks/CorruptionChecker.checkFrequency)); + for (var ptr in CorruptionChecker.ptrs) { + CorruptionChecker.checkPtr(ptr, false); + } + }, +}; + +CorruptionChecker.init(); + 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/intertyper.js b/src/intertyper.js index c1a98354..2103ecfa 100644 --- a/src/intertyper.js +++ b/src/intertyper.js @@ -677,7 +677,7 @@ function intertyper(data, sidePass, baseLineNums) { item.type = item.tokens[1].text; Types.needAnalysis[item.type] = 0; while (['@', '%'].indexOf(item.tokens[2].text[0]) == -1 && !(item.tokens[2].text in PARSABLE_LLVM_FUNCTIONS) && - item.tokens[2].text != 'null' && item.tokens[2].text != 'asm') { + item.tokens[2].text != 'null' && item.tokens[2].text != 'asm' && item.tokens[2].text != 'undef') { assert(item.tokens[2].text != 'asm', 'Inline assembly cannot be compiled to JavaScript!'); item.tokens.splice(2, 1); } @@ -741,10 +741,12 @@ function intertyper(data, sidePass, baseLineNums) { processItem: function(item) { item.intertype = 'atomic'; if (item.tokens[0].text == 'atomicrmw') { + if (item.tokens[1].text == 'volatile') item.tokens.splice(1, 1); item.op = item.tokens[1].text; item.tokens.splice(1, 1); } else { assert(item.tokens[0].text == 'cmpxchg') + if (item.tokens[1].text == 'volatile') item.tokens.splice(1, 1); item.op = 'cmpxchg'; } var last = getTokenIndexByText(item.tokens, ';'); diff --git a/src/jsifier.js b/src/jsifier.js index 761a5fec..ff58ece2 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -375,6 +375,7 @@ function JSify(data, functionsOnly, givenFunctions) { var ret = [item]; item.JS = 'var ' + item.ident + ';'; // Set the actual value in a postset, since it may be a global variable. We also order by dependencies there + Variables.globals[item.ident].targetIdent = item.value.ident; var value = Variables.globals[item.ident].resolvedAlias = finalizeLLVMParameter(item.value); var fix = ''; if (BUILD_AS_SHARED_LIB == 2 && !item.private_) { @@ -478,8 +479,7 @@ function JSify(data, functionsOnly, givenFunctions) { ident = '_' + ident; } var depsText = (deps ? '\n' + deps.map(addFromLibrary).filter(function(x) { return x != '' }).join('\n') : ''); - // redirected idents just need a var, but no value assigned to them - it would be unused - var contentText = isFunction ? snippet : ('var ' + ident + (redirectedIdent ? '' : '=' + snippet) + ';'); + var contentText = isFunction ? snippet : ('var ' + ident + '=' + snippet + ';'); if (ASM_JS) { var sig = LibraryManager.library[ident.substr(1) + '__sig']; if (isFunction && sig && LibraryManager.library[ident.substr(1) + '__asm']) { @@ -506,9 +506,9 @@ function JSify(data, functionsOnly, givenFunctions) { item.JS = ''; } else if (LibraryManager.library.hasOwnProperty(shortident)) { item.JS = addFromLibrary(shortident); - } else { + } else if (!LibraryManager.library.hasOwnProperty(shortident + '__inline')) { item.JS = 'var ' + item.ident + '; // stub for ' + item.ident; - if (WARN_ON_UNDEFINED_SYMBOLS) { + if (WARN_ON_UNDEFINED_SYMBOLS || ASM_JS) { // always warn on undefs in asm, since it breaks validation warn('Unresolved symbol: ' + item.ident); } } @@ -631,15 +631,6 @@ function JSify(data, functionsOnly, givenFunctions) { } } - if (PROFILE) { - func.JS += ' if (PROFILING) { ' - + 'var __parentProfilingNode__ = PROFILING_NODE; PROFILING_NODE = PROFILING_NODE.children["' + func.ident + '"]; ' - + 'if (!PROFILING_NODE) __parentProfilingNode__.children["' + func.ident + '"] = PROFILING_NODE = { time: 0, children: {}, calls: 0 };' - + 'PROFILING_NODE.calls++; ' - + 'var __profilingStartTime__ = Date.now() ' - + '}\n'; - } - if (true) { // TODO: optimize away when not needed if (CLOSURE_ANNOTATIONS) func.JS += '/** @type {number} */'; func.JS += ' var label = 0;\n'; @@ -923,7 +914,11 @@ function JSify(data, functionsOnly, givenFunctions) { case VAR_NATIVIZED: if (isNumber(item.ident)) { // Direct write to a memory address; this may be an intentional segfault, if not, it is a bug in the source - return 'throw "fault on write to ' + item.ident + '";'; + if (ASM_JS) { + return 'abort(' + item.ident + ')'; + } else { + return 'throw "fault on write to ' + item.ident + '";'; + } } return item.ident + '=' + value + ';'; // We have the actual value here break; @@ -1145,12 +1140,6 @@ function JSify(data, functionsOnly, givenFunctions) { }); makeFuncLineActor('return', function(item) { var ret = RuntimeGenerator.stackExit(item.funcData.initialStack, item.funcData.otherStackAllocations) + ';\n'; - if (PROFILE) { - ret += 'if (PROFILING) { ' - + 'PROFILING_NODE.time += Date.now() - __profilingStartTime__; ' - + 'PROFILING_NODE = __parentProfilingNode__ ' - + '}\n'; - } if (LABEL_DEBUG && functionNameFilterTest(item.funcData.ident)) { ret += "Module.print(INDENT + 'Exiting: " + item.funcData.ident + "');\n" + "INDENT = INDENT.substr(0, INDENT.length-2);\n"; @@ -1215,10 +1204,13 @@ function JSify(data, functionsOnly, givenFunctions) { switch (item.op) { case 'add': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, 'tempValue+' + param2, type, null, null, null, null, ',') + ',tempValue)'; case 'sub': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, 'tempValue-' + param2, type, null, null, null, null, ',') + ',tempValue)'; + case 'or': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, 'tempValue|' + param2, type, null, null, null, null, ',') + ',tempValue)'; + case 'and': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, 'tempValue&' + param2, type, null, null, null, null, ',') + ',tempValue)'; + case 'xor': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, 'tempValue^' + param2, type, null, null, null, null, ',') + ',tempValue)'; case 'xchg': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, param2, type, null, null, null, null, ',') + ',tempValue)'; case 'cmpxchg': { var param3 = finalizeLLVMParameter(item.params[2]); - return '(tempValue=' + makeGetValue(param1, 0, type) + ',(' + makeGetValue(param1, 0, type) + '==' + param2 + ' ? ' + makeSetValue(param1, 0, param3, type, null, null, null, null, ',') + ' : 0),tempValue)'; + return '(tempValue=' + makeGetValue(param1, 0, type) + ',(' + makeGetValue(param1, 0, type) + '==(' + param2 + '|0) ? ' + makeSetValue(param1, 0, param3, type, null, null, null, null, ',') + ' : 0),tempValue)'; } default: throw 'unhandled atomic op: ' + item.op; } @@ -1237,6 +1229,15 @@ function JSify(data, functionsOnly, givenFunctions) { var impl = item.ident ? getVarImpl(item.funcData, item.ident) : VAR_EMULATED; switch (impl) { case VAR_NATIVIZED: { + if (isNumber(item.ident)) { + item.assignTo = null; + // Direct read from a memory address; this may be an intentional segfault, if not, it is a bug in the source + if (ASM_JS) { + return 'abort(' + item.ident + ')'; + } else { + return 'throw "fault on read from ' + item.ident + '";'; + } + } return value; // We have the actual value here } case VAR_EMULATED: return makeGetValue(value, 0, item.type, 0, item.unsigned, 0, item.align); @@ -1260,7 +1261,7 @@ function JSify(data, functionsOnly, givenFunctions) { makeFuncLineActor('insertvalue', function(item) { assert(item.indexes.length == 1); // TODO: see extractvalue var ret = '(', ident; - if (item.ident === 'undef') { + if (item.ident === '0') { item.ident = 'tempValue'; ret += item.ident + ' = [' + makeEmptyStruct(item.type) + '], '; } @@ -1300,6 +1301,7 @@ function JSify(data, functionsOnly, givenFunctions) { // We cannot compile assembly. See comment in intertyper.js:'Call' assert(ident != 'asm', 'Inline assembly cannot be compiled to JavaScript!'); + ident = Variables.resolveAliasToIdent(ident); var shortident = ident.slice(1); var callIdent = LibraryManager.getRootIdent(shortident); if (callIdent) { @@ -1417,6 +1419,9 @@ function JSify(data, functionsOnly, givenFunctions) { if (ASM_JS) { assert(returnType.search(/\("'\[,/) == -1); // XXX need isFunctionType(type, out) callIdent = '(' + callIdent + ')&{{{ FTM_' + sig + ' }}}'; // the function table mask is set in emscripten.py + } else if (SAFE_DYNCALLS) { + assert(!ASM_JS, 'cannot emit safe dyncalls in asm'); + callIdent = '(tempInt=' + callIdent + ',tempInt < 0 || tempInt >= FUNCTION_TABLE.length-1 || !FUNCTION_TABLE[tempInt] ? abort("dyncall error: ' + sig + ' " + FUNCTION_TABLE_NAMES[tempInt]) : tempInt)'; } callIdent = Functions.getTable(sig) + '[' + callIdent + ']'; } @@ -1527,7 +1532,7 @@ function JSify(data, functionsOnly, givenFunctions) { print('// ASM_LIBRARY FUNCTIONS'); function fix(f) { // fix indenting to not confuse js optimizer f = f.substr(f.indexOf('f')); // remove initial spaces before 'function' - f = f.substr(0, f.lastIndexOf('\n')+1); // remove spaces and last } + f = f.substr(0, f.lastIndexOf('\n')+1); // remove spaces and last } XXX assumes function has multiple lines return f + '}'; // add unindented } to match function } print(asmLibraryFunctions.map(fix).join('\n')); @@ -1547,6 +1552,10 @@ 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) { print(read('long.js')); } else { @@ -1579,9 +1588,11 @@ 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) - print('// EMSCRIPTEN_GENERATED_FUNCTIONS: ' + JSON.stringify(keys(Functions.implementedFunctions).filter(function(func) { - return IGNORED_FUNCTIONS.indexOf(func.ident) < 0; - })) + '\n'); + 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/library.js b/src/library.js index 1e026937..d787e30c 100644 --- a/src/library.js +++ b/src/library.js @@ -624,7 +624,12 @@ LibraryManager.library = { // dirent.h // ========================================================================== - __dirent_struct_layout: Runtime.generateStructInfo(['d_ino', 'd_name', 'd_off', 'd_reclen', 'd_type'], '%struct.dirent'), + __dirent_struct_layout: Runtime.generateStructInfo([ + ['i32', 'd_ino'], + ['b1024', 'd_name'], + ['i32', 'd_off'], + ['i32', 'd_reclen'], + ['i32', 'd_type']]), opendir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '__dirent_struct_layout'], opendir: function(dirname) { // DIR *opendir(const char *dirname); @@ -786,7 +791,9 @@ LibraryManager.library = { // utime.h // ========================================================================== - __utimbuf_struct_layout: Runtime.generateStructInfo(['actime', 'modtime'], '%struct.utimbuf'), + __utimbuf_struct_layout: Runtime.generateStructInfo([ + ['i32', 'actime'], + ['i32', 'modtime']]), utime__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '__utimbuf_struct_layout'], utime: function(path, times) { // int utime(const char *path, const struct utimbuf *times); @@ -877,23 +884,23 @@ LibraryManager.library = { // ========================================================================== __stat_struct_layout: Runtime.generateStructInfo([ - 'st_dev', - 'st_ino', - 'st_mode', - 'st_nlink', - 'st_uid', - 'st_gid', - 'st_rdev', - 'st_size', - 'st_atime', - 'st_spare1', - 'st_mtime', - 'st_spare2', - 'st_ctime', - 'st_spare3', - 'st_blksize', - 'st_blocks', - 'st_spare4'], '%struct.stat'), + ['i32', 'st_dev'], + ['i32', 'st_ino'], + ['i32', 'st_mode'], + ['i32', 'st_nlink'], + ['i32', 'st_uid'], + ['i32', 'st_gid'], + ['i32', 'st_rdev'], + ['i32', 'st_size'], + ['i32', 'st_atime'], + ['i32', 'st_spare1'], + ['i32', 'st_mtime'], + ['i32', 'st_spare2'], + ['i32', 'st_ctime'], + ['i32', 'st_spare3'], + ['i32', 'st_blksize'], + ['i32', 'st_blocks'], + ['i32', 'st_spare4']]), stat__deps: ['$FS', '__stat_struct_layout'], stat: function(path, buf, dontResolveLastLink) { // http://pubs.opengroup.org/onlinepubs/7908799/xsh/stat.html @@ -1065,17 +1072,17 @@ LibraryManager.library = { // ========================================================================== __statvfs_struct_layout: Runtime.generateStructInfo([ - 'f_bsize', - 'f_frsize', - 'f_blocks', - 'f_bfree', - 'f_bavail', - 'f_files', - 'f_ffree', - 'f_favail', - 'f_fsid', - 'f_flag', - 'f_namemax'], '%struct.statvfs'), + ['i32', 'f_bsize'], + ['i32', 'f_frsize'], + ['i32', 'f_blocks'], + ['i32', 'f_bfree'], + ['i32', 'f_bavail'], + ['i32', 'f_files'], + ['i32', 'f_ffree'], + ['i32', 'f_favail'], + ['i32', 'f_fsid'], + ['i32', 'f_flag'], + ['i32', 'f_namemax']]), statvfs__deps: ['$FS', '__statvfs_struct_layout'], statvfs: function(path, buf) { // http://pubs.opengroup.org/onlinepubs/7908799/xsh/stat.html @@ -1110,12 +1117,12 @@ LibraryManager.library = { // ========================================================================== __flock_struct_layout: Runtime.generateStructInfo([ - 'l_type', - 'l_whence', - 'l_start', - 'l_len', - 'l_pid', - 'l_xxx'], '%struct.flock'), + ['i16', 'l_type'], + ['i16', 'l_whence'], + ['i32', 'l_start'], + ['i32', 'l_len'], + ['i16', 'l_pid'], + ['i16', 'l_xxx']]), open__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '__dirent_struct_layout'], open: function(path, oflag, varargs) { // int open(const char *path, int oflag, ...); @@ -1336,7 +1343,10 @@ LibraryManager.library = { // poll.h // ========================================================================== - __pollfd_struct_layout: Runtime.generateStructInfo(['fd', 'events', 'revents'], '%struct.pollfd'), + __pollfd_struct_layout: Runtime.generateStructInfo([ + ['i32', 'fd'], + ['i16', 'events'], + ['i16', 'revents']]), poll__deps: ['$FS', '__pollfd_struct_layout'], poll: function(fds, nfds, timeout) { // int poll(struct pollfd fds[], nfds_t nfds, int timeout); @@ -2393,6 +2403,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; @@ -2481,6 +2492,17 @@ LibraryManager.library = { continue; } + // TODO: Support strings like "%5c" etc. + if (format[formatIndex] === '%' && format[formatIndex+1] == 'c') { + var argPtr = {{{ makeGetValue('varargs', 'argIndex', 'void*') }}}; + argIndex += Runtime.getNativeFieldSize('void*'); + fields++; + next = get(); + {{{ makeSetValue('argPtr', 0, 'next', 'i8') }}} + formatIndex += 2; + continue; + } + // remove whitespace while (1) { next = get(); @@ -3342,14 +3364,15 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.ECHILD); return -1; }, - perror__deps: ['puts', 'putc', 'strerror', '__errno_location'], + perror__deps: ['puts', 'fputs', 'fputc', 'strerror', '__errno_location'], perror: function(s) { // void perror(const char *s); // http://pubs.opengroup.org/onlinepubs/000095399/functions/perror.html + var stdout = {{{ makeGetValue(makeGlobalUse('_stdout'), '0', 'void*') }}}; if (s) { - _puts(s); - _putc(':'.charCodeAt(0)); - _putc(' '.charCodeAt(0)); + _fputs(s, stdout); + _fputc(':'.charCodeAt(0), stdout); + _fputc(' '.charCodeAt(0), stdout); } var errnum = {{{ makeGetValue('___errno_location()', '0', 'i32') }}}; _puts(_strerror(errnum)); @@ -3647,6 +3670,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) { @@ -3704,93 +3738,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. @@ -3949,11 +3896,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); @@ -4202,6 +4144,8 @@ LibraryManager.library = { return 1; }, + arc4random: 'rand', + // ========================================================================== // string.h // ========================================================================== @@ -4290,8 +4234,9 @@ 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; value4 = value | (value << 8) | (value << 16) | (value << 24); stop4 = stop & ~3; @@ -4502,11 +4447,16 @@ LibraryManager.library = { return 0; }, + memcmp__asm: 'true', + memcmp__sig: 'iiii', memcmp: function(p1, p2, num) { - for (var i = 0; i < num; i++) { - var v1 = {{{ makeGetValue('p1', 'i', 'i8', 0, 1) }}}; - var v2 = {{{ makeGetValue('p2', 'i', 'i8', 0, 1) }}}; - if (v1 != v2) return v1 > v2 ? 1 : -1; + p1 = p1|0; p2 = p2|0; num = num|0; + var i = 0, v1 = 0, v2 = 0; + while ((i|0) < (num|0)) { + var v1 = {{{ makeGetValueAsm('p1', 'i', 'i8', true) }}}; + var v2 = {{{ makeGetValueAsm('p2', 'i', 'i8', true) }}}; + if ((v1|0) != (v2|0)) return ((v1|0) > (v2|0) ? 1 : -1)|0; + i = (i+1)|0; } return 0; }, @@ -4607,10 +4557,8 @@ LibraryManager.library = { __strtok_state: 0, strtok__deps: ['__strtok_state', 'strtok_r'], + strtok__postset: '___strtok_state = Runtime.staticAlloc(4);', strtok: function(s, delim) { - if (!___strtok_state) { - ___strtok_state = _malloc(4); - } return _strtok_r(s, delim, ___strtok_state); }, @@ -4948,6 +4896,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; }, @@ -5070,6 +5032,7 @@ LibraryManager.library = { }, __cxa_call_unexpected: function(exception) { + Module.printErr('Unexpected exception thrown, this is not properly supported - aborting'); ABORT = true; throw exception; }, @@ -5477,6 +5440,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); }, @@ -5619,11 +5590,11 @@ LibraryManager.library = { // ========================================================================== __utsname_struct_layout: Runtime.generateStructInfo([ - 'sysname', - 'nodename', - 'release', - 'version', - 'machine'], '%struct.utsname'), + ['b32', 'sysname'], + ['b32', 'nodename'], + ['b32', 'release'], + ['b32', 'version'], + ['b32', 'machine']]), uname__deps: ['__utsname_struct_layout'], uname: function(name) { // int uname(struct utsname *name); @@ -5823,17 +5794,17 @@ LibraryManager.library = { }, __tm_struct_layout: Runtime.generateStructInfo([ - 'tm_sec', - 'tm_min', - 'tm_hour', - 'tm_mday', - 'tm_mon', - 'tm_year', - 'tm_wday', - 'tm_yday', - 'tm_isdst', - 'tm_gmtoff', - 'tm_zone'], '%struct.tm'), + ['i32', 'tm_sec'], + ['i32', 'tm_min'], + ['i32', 'tm_hour'], + ['i32', 'tm_mday'], + ['i32', 'tm_mon'], + ['i32', 'tm_year'], + ['i32', 'tm_wday'], + ['i32', 'tm_yday'], + ['i32', 'tm_isdst'], + ['i32', 'tm_gmtoff'], + ['i32', 'tm_zone']]), // Statically allocated time struct. __tm_current: 'allocate({{{ Runtime.QUANTUM_SIZE }}}*26, "i8", ALLOC_STACK)', // Statically allocated timezone strings. @@ -6032,7 +6003,18 @@ LibraryManager.library = { // sys/time.h // ========================================================================== - __timespec_struct_layout: Runtime.generateStructInfo(['tv_sec', 'tv_nsec'], '%struct.timespec'), + __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) { @@ -6089,10 +6071,10 @@ LibraryManager.library = { // ========================================================================== __tms_struct_layout: Runtime.generateStructInfo([ - 'tms_utime', - 'tms_stime', - 'tms_cutime', - 'tms_cstime'], '%struct.tms'), + ['i32', 'tms_utime'], + ['i32', 'tms_stime'], + ['i32', 'tms_cutime'], + ['i32', 'tms_cstime']]), times__deps: ['__tms_struct_layout', 'memset'], times: function(buffer) { // clock_t times(struct tms *buffer); @@ -6139,7 +6121,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) { @@ -6614,7 +6596,9 @@ LibraryManager.library = { // ========================================================================== // TODO: Implement for real. - __rlimit_struct_layout: Runtime.generateStructInfo(['rlim_cur', 'rlim_max'], '%struct.rlimit'), + __rlimit_struct_layout: Runtime.generateStructInfo([ + ['i32', 'rlim_cur'], + ['i32', 'rlim_max']]), getrlimit__deps: ['__rlimit_struct_layout'], getrlimit: function(resource, rlp) { // int getrlimit(int resource, struct rlimit *rlp); @@ -6630,22 +6614,22 @@ LibraryManager.library = { // TODO: Implement for real. We just do time used, and no useful data __rusage_struct_layout: Runtime.generateStructInfo([ - 'ru_utime', - 'ru_stime', - 'ru_maxrss', - 'ru_ixrss', - 'ru_idrss', - 'ru_isrss', - 'ru_minflt', - 'ru_majflt', - 'ru_nswap', - 'ru_inblock', - 'ru_oublock', - 'ru_msgsnd', - 'ru_msgrcv', - 'ru_nsignals', - 'ru_nvcsw', - 'ru_nivcsw'], '%struct.rusage'), + ['i64', 'ru_utime'], + ['i64', 'ru_stime'], + ['i32', 'ru_maxrss'], + ['i32', 'ru_ixrss'], + ['i32', 'ru_idrss'], + ['i32', 'ru_isrss'], + ['i32', 'ru_minflt'], + ['i32', 'ru_majflt'], + ['i32', 'ru_nswap'], + ['i32', 'ru_inblock'], + ['i32', 'ru_oublock'], + ['i32', 'ru_msgsnd'], + ['i32', 'ru_msgrcv'], + ['i32', 'ru_nsignals'], + ['i32', 'ru_nvcsw'], + ['i32', 'ru_nivcsw']]), getrusage__deps: ['__rusage_struct_layout'], getrusage: function(resource, rlp) { // %struct.timeval = type { i32, i32 } @@ -6660,6 +6644,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!) // ========================================================================== @@ -6670,10 +6661,20 @@ LibraryManager.library = { pthread_mutexattr_destroy: function() {}, pthread_mutex_lock: function() {}, pthread_mutex_unlock: function() {}, + pthread_mutex_trylock: function() { + return 0; + }, 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; @@ -6712,17 +6713,27 @@ LibraryManager.library = { pthread_key_create: function(key, destructor) { if (!_pthread_key_create.keys) _pthread_key_create.keys = {}; - _pthread_key_create.keys[key] = null; + // values start at 0 + _pthread_key_create.keys[key] = 0; }, pthread_getspecific: function(key) { - return _pthread_key_create.keys[key]; + return _pthread_key_create.keys[key] || 0; }, pthread_setspecific: function(key, value) { _pthread_key_create.keys[key] = value; }, + pthread_key_delete: ['$ERRNO_CODES'], + pthread_key_delete: function(key) { + if (_pthread_key_create.keys[key]) { + delete _pthread_key_create.keys[key]; + return 0; + } + return ERRNO_CODES.EINVAL; + }, + pthread_cleanup_push: function(routine, arg) { __ATEXIT__.push({ func: function() { Runtime.dynCall('vi', routine, [arg]) } }) _pthread_cleanup_push.level = __ATEXIT__.length; @@ -7227,23 +7238,56 @@ LibraryManager.library = { }, select: function(nfds, readfds, writefds, exceptfds, timeout) { - // only readfds are supported, not writefds or exceptfds + // readfds are supported, + // writefds checks socket open status + // exceptfds not supported // timeout is always 0 - fully async - assert(!writefds && !exceptfds); - var ret = 0; - var l = {{{ makeGetValue('readfds', 0, 'i32') }}}; - var h = {{{ makeGetValue('readfds', 4, 'i32') }}}; - nfds = Math.min(64, nfds); // fd sets have 64 bits - for (var fd = 0; fd < nfds; fd++) { - var bit = fd % 32, int = fd < 32 ? l : h; - if (int & (1 << bit)) { - // index is in the set, check if it is ready for read - var info = Sockets.fds[fd]; - if (!info) continue; - if (info.hasData()) ret++; + assert(!exceptfds); + + function canRead(info) { + // make sure hasData exists. + // we do create it when the socket is connected, + // but other implementations may create it lazily + return info.hasData && info.hasData(); + } + + function canWrite(info) { + // make sure socket exists. + // we do create it when the socket is connected, + // but other implementations may create it lazily + return info.socket && (info.socket.readyState == info.socket.OPEN); + } + + function checkfds(nfds, fds, can) { + if (!fds) return 0; + + var bitsSet = 0; + var dstLow = 0; + var dstHigh = 0; + var srcLow = {{{ makeGetValue('fds', 0, 'i32') }}}; + var srcHigh = {{{ makeGetValue('fds', 4, 'i32') }}}; + nfds = Math.min(64, nfds); // fd sets have 64 bits + + for (var fd = 0; fd < nfds; fd++) { + var mask = 1 << (fd % 32), int = fd < 32 ? srcLow : srcHigh; + if (int & mask) { + // index is in the set, check if it is ready for read + var info = Sockets.fds[fd]; + if (info && can(info)) { + // set bit + fd < 32 ? (dstLow = dstLow | mask) : (dstHigh = dstHigh | mask); + bitsSet++; + } + } } + + {{{ makeSetValue('fds', 0, 'dstLow', 'i32') }}}; + {{{ makeSetValue('fds', 4, 'dstHigh', 'i32') }}}; + return bitsSet; } - return ret; + + return checkfds(nfds, readfds, canRead) + + checkfds(nfds, writefds, canWrite); }, // pty.h @@ -7290,35 +7334,22 @@ LibraryManager.library = { return Math.random(); }, - $Profiling: { - max_: 0, - times: null, - invalid: 0, - dump: function() { - if (Profiling.invalid) { - Module.printErr('Invalid # of calls to Profiling begin and end!'); - return; - } - Module.printErr('Profiling data:') - for (var i = 0; i < Profiling.max_; i++) { - Module.printErr('Block ' + i + ': ' + Profiling.times[i]); - } + emscripten_jcache_printf___deps: ['_formatString'], + emscripten_jcache_printf_: function(varargs) { + var MAX = 10240; + if (!_emscripten_jcache_printf_.buffer) { + _emscripten_jcache_printf_.buffer = _malloc(MAX); } + var i = 0; + do { + var curr = {{{ makeGetValue('varargs', 'i*4', 'i8') }}}; + {{{ makeSetValue('_emscripten_jcache_printf_.buffer', 'i', 'curr', 'i8') }}}; + i++; + assert(i*4 < MAX); + } while (curr != 0); + 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 }, - EMSCRIPTEN_PROFILE_INIT__deps: ['$Profiling'], - EMSCRIPTEN_PROFILE_INIT: function(max_) { - Profiling.max_ = max_; - Profiling.times = new Array(max_); - for (var i = 0; i < max_; i++) Profiling.times[i] = 0; - }, - EMSCRIPTEN_PROFILE_BEGIN__inline: function(id) { - return 'Profiling.times[' + id + '] -= Date.now();' - + 'Profiling.invalid++;' - }, - EMSCRIPTEN_PROFILE_END__inline: function(id) { - return 'Profiling.times[' + id + '] += Date.now();' - + 'Profiling.invalid--;' - } }; function autoAddDeps(object, name) { diff --git a/src/library_browser.js b/src/library_browser.js index d16fbc0b..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: [], @@ -68,6 +69,7 @@ mergeInto(LibraryManager.library, { function getMimetype(name) { return { 'jpg': 'image/jpeg', + 'jpeg': 'image/jpeg', 'png': 'image/png', 'bmp': 'image/bmp', 'ogg': 'audio/ogg', @@ -81,7 +83,7 @@ mergeInto(LibraryManager.library, { var imagePlugin = {}; imagePlugin['canHandle'] = function(name) { - return name.substr(-4) in { '.jpg': 1, '.png': 1, '.bmp': 1 }; + return !Module.noImageDecoding && /\.(jpg|jpeg|png|bmp)$/.exec(name); }; imagePlugin['handle'] = function(byteArray, name, onload, onerror) { var b = null; @@ -123,7 +125,7 @@ mergeInto(LibraryManager.library, { var audioPlugin = {}; audioPlugin['canHandle'] = function(name) { - return name.substr(-4) in { '.ogg': 1, '.wav': 1, '.mp3': 1 }; + return !Module.noAudioDecoding && name.substr(-4) in { '.ogg': 1, '.wav': 1, '.mp3': 1 }; }; audioPlugin['handle'] = function(byteArray, name, onload, onerror) { var done = false; @@ -200,8 +202,18 @@ mergeInto(LibraryManager.library, { return null; } #endif + var ctx; try { - var ctx = canvas.getContext(useWebGL ? 'experimental-webgl' : '2d'); + if (useWebGL) { + ctx = canvas.getContext('experimental-webgl', { +#if GL_TESTING + preserveDrawingBuffer: true, +#endif + alpha: false + }); + } else { + ctx = canvas.getContext('2d'); + } if (!ctx) throw ':('; } catch (e) { Module.print('Could not create canvas - ' + e); @@ -263,35 +275,59 @@ mergeInto(LibraryManager.library, { return ctx; }, - requestFullScreen: function() { + destroyContext: function(canvas, useWebGL, setInModule) {}, + + 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'] || @@ -369,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_egl.js b/src/library_egl.js index a9eb37dd..271ea29e 100644 --- a/src/library_egl.js +++ b/src/library_egl.js @@ -83,6 +83,17 @@ var LibraryEGL = { return 1; }, +// EGLAPI EGLBoolean EGLAPIENTRY eglTerminate(EGLDisplay dpy); + eglTerminate: function(display) { + if (display != 62000 /* Magic ID for Emscripten 'default display' */) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + // TODO: Tear down EGL here. Currently a no-op since we don't need to actually do anything here for the browser. + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }, + // EGLAPI EGLBoolean EGLAPIENTRY eglGetConfigs(EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config); eglGetConfigs: function(display, configs, config_size, numConfigs) { return EGL.chooseConfig(display, 0, configs, config_size, numConfigs); @@ -225,6 +236,20 @@ var LibraryEGL = { return 62006; /* Magic ID for Emscripten 'default surface' */ }, + // EGLAPI EGLBoolean EGLAPIENTRY eglDestroySurface(EGLDisplay display, EGLSurface surface); + eglDestroySurface: function(display, surface) { + if (display != 62000 /* Magic ID for Emscripten 'default display' */) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if (surface != 62006 /* Magic ID for the only EGLSurface supported by Emscripten */) { + EGL.setErrorCode(0x300D /* EGL_BAD_SURFACE */); + return 1; + } + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; /* Magic ID for Emscripten 'default surface' */ + }, + eglCreateContext__deps: ['glutCreateWindow', '$GL'], // EGLAPI EGLContext EGLAPIENTRY eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list); @@ -234,7 +259,21 @@ var LibraryEGL = { return 0; } - _glutCreateWindow(); + EGL.windowID = _glutCreateWindow(); + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 62004; // Magic ID for Emscripten EGLContext + }, + + eglDestroyContext__deps: ['glutDestroyWindow', '$GL'], + + // EGLAPI EGLBoolean EGLAPIENTRY eglDestroyContext(EGLDisplay dpy, EGLContext context); + eglDestroyContext: function(display, context) { + if (display != 62000 /* Magic ID for Emscripten 'default display' */) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + + _glutDestroyWindow(EGL.windowID); EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); return 62004; // Magic ID for Emscripten EGLContext }, diff --git a/src/library_gc.js b/src/library_gc.js index 083019ca..f6db74d8 100644 --- a/src/library_gc.js +++ b/src/library_gc.js @@ -4,7 +4,7 @@ if (GC_SUPPORT) { EXPORTED_FUNCTIONS['_realloc'] = 1; var LibraryGC = { - $GC__deps: ['sbrk', 'realloc'], + $GC__deps: ['sbrk', 'realloc', 'calloc'], $GC: { ALLOCATIONS_TO_GC: 1*1024*1024, diff --git a/src/library_gl.js b/src/library_gl.js index f7966dab..c6007809 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -19,6 +19,27 @@ var LibraryGL = { uniforms: [], shaders: [], +#if FULL_ES2 + clientBuffers: [], +#endif + currArrayBuffer: 0, + currElementArrayBuffer: 0, + + byteSizeByTypeRoot: 0x1400, // GL_BYTE + byteSizeByType: [ + 1, // GL_BYTE + 1, // GL_UNSIGNED_BYTE + 2, // GL_SHORT + 2, // GL_UNSIGNED_SHORT + 4, // GL_INT + 4, // GL_UNSIGNED_INT + 4, // GL_FLOAT + 2, // GL_2_BYTES + 3, // GL_3_BYTES + 4, // GL_4_BYTES + 8 // GL_DOUBLE + ], + uniformTable: {}, // name => uniform ID. the uID must be identical until relinking, cannot create a new uID each call to glGetUniformLocation packAlignment: 4, // default alignment is 4 bytes @@ -37,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) { @@ -176,12 +256,70 @@ var LibraryGL = { } }, +#if FULL_ES2 + calcBufLength: function(size, type, stride, count) { + if (stride > 0) { + return count * stride; // XXXvlad this is not exactly correct I don't think + } + var typeSize = GL.byteSizeByType[type - GL.byteSizeByTypeRoot]; + 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) { + var cb = GL.clientBuffers[i]; + if (!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 + size), + Module.ctx.DYNAMIC_DRAW); + Module.ctx.vertexAttribPointer(i, cb.size, cb.type, cb.normalized, cb.stride, 0); + } + }, + + postDrawHandleClientVertexAttribBindings: function() { + if (GL.resetBufferBinding) { + Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, GL.buffers[GL.currArrayBuffer]); + } + }, +#endif + initExtensions: function() { if (GL.initExtensions.done) return; GL.initExtensions.done = true; 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, 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') || Module.ctx.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc'); @@ -408,7 +546,19 @@ var LibraryGL = { }, glReadPixels: function(x, y, width, height, format, type, pixels) { - Module.ctx.readPixels(x, y, width, height, format, type, HEAPU8.subarray(pixels)); + assert(type == 0x1401 /* GL_UNSIGNED_BYTE */); + var sizePerPixel; + switch (format) { + case 0x1907 /* GL_RGB */: + sizePerPixel = 3; + break; + case 0x1908 /* GL_RGBA */: + sizePerPixel = 4; + break; + default: throw 'unsupported glReadPixels format'; + } + var totalSize = width*height*sizePerPixel; + Module.ctx.readPixels(x, y, width, height, format, type, HEAPU8.subarray(pixels, pixels + totalSize)); }, glBindTexture: function(target, texture) { @@ -444,6 +594,9 @@ var LibraryGL = { var id = {{{ makeGetValue('buffers', 'i*4', 'i32') }}}; Module.ctx.deleteBuffer(GL.buffers[id]); GL.buffers[id] = null; + + if (id == GL.currArrayBuffer) GL.currArrayBuffer = 0; + if (id == GL.currElementArrayBuffer) GL.currElementArrayBuffer = 0; } }, @@ -462,11 +615,9 @@ var LibraryGL = { }, glIsBuffer: function(buffer) { - var fb = GL.buffers[buffer]; - if (typeof(fb) == 'undefined') { - return 0; - } - return Module.ctx.isBuffer(fb); + var b = GL.buffers[buffer]; + if (!b) return 0; + return Module.ctx.isBuffer(b); }, glGenRenderbuffers__sig: 'vii', @@ -497,11 +648,9 @@ var LibraryGL = { }, glIsRenderbuffer: function(renderbuffer) { - var fb = GL.renderbuffers[renderbuffer]; - if (typeof(fb) == 'undefined') { - return 0; - } - return Module.ctx.isRenderbuffer(fb); + var rb = GL.renderbuffers[renderbuffer]; + if (!rb) return 0; + return Module.ctx.isRenderbuffer(rb); }, glGetUniformfv: function(program, location, params) { @@ -542,6 +691,11 @@ var LibraryGL = { }, glGetVertexAttribfv: function(index, pname, params) { +#if FULL_ES2 + if (GL.clientBuffers[index].enabled) { + Module.printErr("glGetVertexAttribfv on client-side array: not supported, bad data returned"); + } +#endif var data = Module.ctx.getVertexAttrib(index, pname); if (typeof data == 'number') { {{{ makeSetValue('params', '0', 'data', 'float') }}}; @@ -553,6 +707,11 @@ var LibraryGL = { }, glGetVertexAttribiv: function(index, pname, params) { +#if FULL_ES2 + if (GL.clientBuffers[index].enabled) { + Module.printErr("glGetVertexAttribiv on client-side array: not supported, bad data returned"); + } +#endif var data = Module.ctx.getVertexAttrib(index, pname); if (typeof data == 'number' || typeof data == 'boolean') { {{{ makeSetValue('params', '0', 'data', 'i32') }}}; @@ -564,6 +723,11 @@ var LibraryGL = { }, glGetVertexAttribPointerv: function(index, pname, pointer) { +#if FULL_ES2 + if (GL.clientBuffers[index].enabled) { + Module.printErr("glGetVertexAttribPointer on client-side array: not supported, bad data returned"); + } +#endif {{{ makeSetValue('pointer', '0', 'Module.ctx.getVertexAttribOffset(index, pname)', 'i32') }}}; }, @@ -719,6 +883,12 @@ var LibraryGL = { glBindBuffer__sig: 'vii', glBindBuffer: function(target, buffer) { + if (target == Module.ctx.ARRAY_BUFFER) { + GL.currArrayBuffer = buffer; + } else if (target == Module.ctx.ELEMENT_ARRAY_BUFFER) { + GL.currElementArrayBuffer = buffer; + } + Module.ctx.bindBuffer(target, buffer ? GL.buffers[buffer] : null); }, @@ -846,11 +1016,9 @@ var LibraryGL = { }, glIsShader: function(shader) { - var fb = GL.shaders[shader]; - if (typeof(fb) == 'undefined') { - return 0; - } - return Module.ctx.isShader(fb); + var s = GL.shaders[shader]; + if (!s) return 0; + return Module.ctx.isShader(s); }, glCreateProgram__sig: 'i', @@ -908,11 +1076,9 @@ var LibraryGL = { }, glIsProgram: function(program) { - var fb = GL.programs[program]; - if (typeof(fb) == 'undefined') { - return 0; - } - return Module.ctx.isProgram(fb); + var program = GL.programs[program]; + if (!program) return 0; + return Module.ctx.isProgram(program); }, glBindAttribLocation__sig: 'viii', @@ -965,9 +1131,7 @@ var LibraryGL = { glIsFramebuffer__sig: 'ii', glIsFramebuffer: function(framebuffer) { var fb = GL.framebuffers[framebuffer]; - if (typeof(fb) == 'undefined') { - return 0; - } + if (!fb) return 0; return Module.ctx.isFramebuffer(fb); }, @@ -983,6 +1147,11 @@ var LibraryGL = { fogMode: 0x0800, // GL_EXP fogEnabled: false, + // VAO support + vaos: [], + currentVao: null, + enabledVertexAttribArrays: {}, // helps with vao cleanups + init: function() { GLEmulation.fogColor = new Float32Array(4); @@ -1001,6 +1170,7 @@ var LibraryGL = { 0x809E: 1, // GL_SAMPLE_ALPHA_TO_COVERAGE 0x80A0: 1 // GL_SAMPLE_COVERAGE }; + _glEnable = function(cap) { // Clean up the renderer on any change to the rendering state. The optimization of // skipping renderer setup is aimed at the case of multiple glDraw* right after each other @@ -1008,6 +1178,11 @@ var LibraryGL = { if (cap == 0x0B60 /* GL_FOG */) { GLEmulation.fogEnabled = true; return; + } else if (cap == 0x0de1 /* GL_TEXTURE_2D */) { + // XXX not according to spec, and not in desktop GL, but works in some GLES1.x apparently, so support + // it by forwarding to glEnableClientState + _glEnableClientState(cap); + return; } else if (!(cap in validCapabilities)) { return; } @@ -1018,6 +1193,11 @@ var LibraryGL = { if (cap == 0x0B60 /* GL_FOG */) { GLEmulation.fogEnabled = false; return; + } else if (cap == 0x0de1 /* GL_TEXTURE_2D */) { + // XXX not according to spec, and not in desktop GL, but works in some GLES1.x apparently, so support + // it by forwarding to glDisableClientState + _glDisableClientState(cap); + return; } else if (!(cap in validCapabilities)) { return; } @@ -1032,6 +1212,17 @@ var LibraryGL = { return Module.ctx.isEnabled(cap); }; + var glGetBooleanv = _glGetBooleanv; + _glGetBooleanv = function(pname, p) { + var attrib = GLEmulation.getAttributeFromCapability(pname); + if (attrib !== null) { + var result = GL.immediate.enabledClientAttributes[attrib]; + {{{ makeSetValue('p', '0', 'result === true ? 1 : 0', 'i8') }}}; + return; + } + glGetBooleanv(pname, p); + }; + var glGetIntegerv = _glGetIntegerv; _glGetIntegerv = function(pname, params) { switch (pname) { @@ -1052,6 +1243,51 @@ var LibraryGL = { return; } case 0x8871: pname = Module.ctx.MAX_COMBINED_TEXTURE_IMAGE_UNITS /* close enough */; break; // GL_MAX_TEXTURE_COORDS + case 0x807A: { // GL_VERTEX_ARRAY_SIZE + var attribute = GLImmediate.clientAttributes[GLImmediate.VERTEX]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.size : 0', 'i32') }}}; + return; + } + case 0x807B: { // GL_VERTEX_ARRAY_TYPE + var attribute = GLImmediate.clientAttributes[GLImmediate.VERTEX]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.type : 0', 'i32') }}}; + return; + } + case 0x807C: { // GL_VERTEX_ARRAY_STRIDE + var attribute = GLImmediate.clientAttributes[GLImmediate.VERTEX]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.stride : 0', 'i32') }}}; + return; + } + case 0x8081: { // GL_COLOR_ARRAY_SIZE + var attribute = GLImmediate.clientAttributes[GLImmediate.COLOR]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.size : 0', 'i32') }}}; + return; + } + case 0x8082: { // GL_COLOR_ARRAY_TYPE + var attribute = GLImmediate.clientAttributes[GLImmediate.COLOR]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.type : 0', 'i32') }}}; + return; + } + case 0x8083: { // GL_COLOR_ARRAY_STRIDE + var attribute = GLImmediate.clientAttributes[GLImmediate.COLOR]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.stride : 0', 'i32') }}}; + return; + } + case 0x8088: { // GL_TEXTURE_COORD_ARRAY_SIZE + var attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.size : 0', 'i32') }}}; + return; + } + case 0x8089: { // GL_TEXTURE_COORD_ARRAY_TYPE + var attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.type : 0', 'i32') }}}; + return; + } + case 0x808A: { // GL_TEXTURE_COORD_ARRAY_STRIDE + var attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.stride : 0', 'i32') }}}; + return; + } } glGetIntegerv(pname, params); }; @@ -1266,19 +1502,12 @@ var LibraryGL = { _glBindBuffer = function(target, buffer) { glBindBuffer(target, buffer); if (target == Module.ctx.ARRAY_BUFFER) { - GL.currArrayBuffer = buffer; + if (GLEmulation.currentVao) { + assert(GLEmulation.currentVao.arrayBuffer == buffer || GLEmulation.currentVao.arrayBuffer == 0 || buffer == 0, 'TODO: support for multiple array buffers in vao'); + GLEmulation.currentVao.arrayBuffer = buffer; + } } else if (target == Module.ctx.ELEMENT_ARRAY_BUFFER) { - GL.currElementArrayBuffer = buffer; - } - }; - - var glDeleteBuffers = _glDeleteBuffers; - _glDeleteBuffers = function(n, buffers) { - glDeleteBuffers(n, buffers); - for (var i = 0; i < n; i++) { - var buffer = {{{ makeGetValue('buffers', 'i*4', 'i32') }}}; - if (buffer == GL.currArrayBuffer) GL.currArrayBuffer = 0; - if (buffer == GL.currElementArrayBuffer) GL.currElementArrayBuffer = 0; + if (GLEmulation.currentVao) GLEmulation.currentVao.elementArrayBuffer = buffer; } }; @@ -1312,6 +1541,44 @@ var LibraryGL = { } glHint(target, mode); }; + + var glEnableVertexAttribArray = _glEnableVertexAttribArray; + _glEnableVertexAttribArray = function(index) { + glEnableVertexAttribArray(index); + GLEmulation.enabledVertexAttribArrays[index] = 1; + if (GLEmulation.currentVao) GLEmulation.currentVao.enabledVertexAttribArrays[index] = 1; + }; + + var glDisableVertexAttribArray = _glDisableVertexAttribArray; + _glDisableVertexAttribArray = function(index) { + glDisableVertexAttribArray(index); + delete GLEmulation.enabledVertexAttribArrays[index]; + if (GLEmulation.currentVao) delete GLEmulation.currentVao.enabledVertexAttribArrays[index]; + }; + + var glVertexAttribPointer = _glVertexAttribPointer; + _glVertexAttribPointer = function(index, size, type, normalized, stride, pointer) { + glVertexAttribPointer(index, size, type, normalized, stride, pointer); + if (GLEmulation.currentVao) { // TODO: avoid object creation here? likely not hot though + GLEmulation.currentVao.vertexAttribPointers[index] = [index, size, type, normalized, stride, pointer]; + } + }; + }, + + getAttributeFromCapability: function(cap) { + var attrib = null; + switch (cap) { + case 0x8078: // GL_TEXTURE_COORD_ARRAY + case 0x0de1: // GL_TEXTURE_2D - XXX not according to spec, and not in desktop GL, but works in some GLES1.x apparently, so support it + attrib = GL.immediate.TEXTURE0 + GL.immediate.clientActiveTexture; break; + case 0x8074: // GL_VERTEX_ARRAY + attrib = GL.immediate.VERTEX; break; + case 0x8075: // GL_NORMAL_ARRAY + attrib = GL.immediate.NORMAL; break; + case 0x8076: // GL_COLOR_ARRAY + attrib = GL.immediate.COLOR; break; + } + return attrib; }, getProcAddress: function(name) { @@ -1375,6 +1642,9 @@ var LibraryGL = { case 'glIsFramebuffer': ret = {{{ Functions.getIndex('_glIsFramebuffer', true) }}}; break; case 'glCheckFramebufferStatus': ret = {{{ Functions.getIndex('_glCheckFramebufferStatus', true) }}}; break; case 'glRenderbufferStorage': ret = {{{ Functions.getIndex('_glRenderbufferStorage', true) }}}; break; + case 'glGenVertexArrays': ret = {{{ Functions.getIndex('_glGenVertexArrays', true) }}}; break; + case 'glDeleteVertexArrays': ret = {{{ Functions.getIndex('_glDeleteVertexArrays', true) }}}; break; + case 'glBindVertexArray': ret = {{{ Functions.getIndex('_glBindVertexArray', true) }}}; break; } if (!ret) Module.printErr('WARNING: getProcAddress failed for ' + name); return ret; @@ -1427,10 +1697,24 @@ var LibraryGL = { assert(id == 0); }, + glGetPointerv: function(name, p) { + var attribute; + switch(name) { + case 0x808E: // GL_VERTEX_ARRAY_POINTER + attribute = GLImmediate.clientAttributes[GLImmediate.VERTEX]; break; + case 0x8090: // GL_COLOR_ARRAY_POINTER + attribute = GLImmediate.clientAttributes[GLImmediate.COLOR]; break; + case 0x8092: // GL_TEXTURE_COORD_ARRAY_POINTER + attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0]; break; + default: throw 'TODO: glGetPointerv for ' + name; + } + {{{ makeSetValue('p', '0', 'attribute ? attribute.pointer : 0', 'i32') }}}; + }, + // GL Immediate mode $GLImmediate__postset: 'GL.immediate.setupFuncs(); Browser.moduleContextCreatedCallbacks.push(function() { GL.immediate.init() });', - $GLImmediate__deps: ['$Browser', '$GL'], + $GLImmediate__deps: ['$Browser', '$GL', '$GLEmulation'], $GLImmediate: { MAX_TEXTURES: 7, @@ -1479,17 +1763,6 @@ var LibraryGL = { clientActiveTexture: 0, clientColor: null, - byteSizeByTypeRoot: 0x1400, // GL_BYTE - byteSizeByType: [ - 1, // GL_BYTE - 1, // GL_UNSIGNED_BYTE - 2, // GL_SHORT - 2, // GL_UNSIGNED_SHORT - 4, // GL_INT - 4, // GL_UNSIGNED_INT - 4 // GL_FLOAT - ], - setClientAttribute: function(name, size, type, stride, pointer) { var attrib = this.clientAttributes[name]; attrib.name = name; @@ -1500,62 +1773,6 @@ var LibraryGL = { this.modifiedClientAttributes = true; }, - // Temporary buffers - MAX_TEMP_BUFFER_SIZE: 2*1024*1024, - 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]) { @@ -1565,7 +1782,7 @@ var LibraryGL = { #endif this.enabledClientAttributes[name] = true; this.setClientAttribute(name, size, type, 0, this.rendererComponentPointer); - this.rendererComponentPointer += size * this.byteSizeByType[type - this.byteSizeByTypeRoot]; + this.rendererComponentPointer += size * GL.byteSizeByType[type - GL.byteSizeByTypeRoot]; } else { this.rendererComponents[name]++; } @@ -1589,7 +1806,7 @@ var LibraryGL = { cacheItem = temp ? temp : (cacheItem[attribute.name] = GL.immediate.rendererCacheItemTemplate.slice()); temp = cacheItem[attribute.size]; cacheItem = temp ? temp : (cacheItem[attribute.size] = GL.immediate.rendererCacheItemTemplate.slice()); - var typeIndex = attribute.type - GL.immediate.byteSizeByTypeRoot; // ensure it starts at 0 to keep the cache items dense + var typeIndex = attribute.type - GL.byteSizeByTypeRoot; // ensure it starts at 0 to keep the cache items dense temp = cacheItem[typeIndex]; cacheItem = temp ? temp : (cacheItem[typeIndex] = GL.immediate.rendererCacheItemTemplate.slice()); } @@ -1776,35 +1993,43 @@ 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; } // If the array buffer is unchanged and the renderer as well, then we can avoid all the work here - // XXX We use some heuristics here, and this may not work in all cases. Try disabling this if you - // have odd glitches (by setting canSkip always to 0, or even cleaning up the renderer right - // after rendering) + // XXX We use some heuristics here, and this may not work in all cases. Try disabling GL_UNSAFE_OPTS if you + // have odd glitches +#if GL_UNSAFE_OPTS var lastRenderer = GL.immediate.lastRenderer; var canSkip = this == lastRenderer && arrayBuffer == GL.immediate.lastArrayBuffer && (GL.currProgram || this.program) == GL.immediate.lastProgram && !GL.immediate.matricesModified; if (!canSkip && lastRenderer) lastRenderer.cleanup(); +#endif if (!GL.currArrayBuffer) { // Bind the array buffer and upload data after cleaning up the previous renderer +#if GL_UNSAFE_OPTS + // Potentially unsafe, since lastArrayBuffer might not reflect the true array buffer in code that mixes immediate/non-immediate if (arrayBuffer != GL.immediate.lastArrayBuffer) { +#endif Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, arrayBuffer); +#if GL_UNSAFE_OPTS } +#endif Module.ctx.bufferSubData(Module.ctx.ARRAY_BUFFER, start, GL.immediate.vertexData.subarray(start >> 2, end >> 2)); } +#if GL_UNSAFE_OPTS if (canSkip) return; GL.immediate.lastRenderer = this; GL.immediate.lastArrayBuffer = arrayBuffer; GL.immediate.lastProgram = GL.currProgram || this.program; GL.immediate.matricesModified = false; +#endif if (!GL.currProgram) { Module.ctx.useProgram(this.program); @@ -1880,9 +2105,11 @@ var LibraryGL = { Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, null); } +#if GL_UNSAFE_OPTS GL.immediate.lastRenderer = null; GL.immediate.lastArrayBuffer = null; GL.immediate.lastProgram = null; +#endif GL.immediate.matricesModified = true; } }; @@ -1958,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]); }, @@ -1997,22 +2224,61 @@ var LibraryGL = { #endif if (attribute.stride) stride = attribute.stride; } - - var bytes = 0; - for (var i = 0; i < attributes.length; i++) { - var attribute = attributes[i]; - if (!attribute) break; - attribute.offset = attribute.pointer - start; - if (attribute.offset > bytes) { // ensure we start where we should - assert((attribute.offset - bytes)%4 == 0); // XXX assuming 4-alignment - bytes += attribute.offset - bytes; + var bytes = 0; // total size in bytes + if (!stride && !beginEnd) { + // beginEnd can not have stride in the attributes, that is fine. otherwise, + // no stride means that all attributes are in fact packed. to keep the rest of + // our emulation code simple, we perform unpacking/restriding here. this adds overhead, so + // it is a good idea to not hit this! +#if ASSERTIONS + Runtime.warnOnce('Unpacking/restriding attributes, this is not fast'); +#endif + if (!GL.immediate.restrideBuffer) GL.immediate.restrideBuffer = _malloc(GL.MAX_TEMP_BUFFER_SIZE); + start = GL.immediate.restrideBuffer; +#if ASSERTIONS + assert(start % 4 == 0); +#endif + // calculate restrided offsets and total size + for (var i = 0; i < attributes.length; i++) { + var attribute = attributes[i]; + if (!attribute) break; + var size = attribute.size * GL.byteSizeByType[attribute.type - GL.byteSizeByTypeRoot]; + if (size % 4 != 0) size += 4 - (size % 4); // align everything + attribute.offset = bytes; + bytes += size; + } +#if ASSERTIONS + 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++) { + var attribute = attributes[i]; + if (!attribute) break; + var size4 = Math.floor((attribute.size * GL.byteSizeByType[attribute.type - GL.byteSizeByTypeRoot])/4); + for (var j = 0; j < count; j++) { + for (var k = 0; k < size4; k++) { // copy in chunks of 4 bytes, our alignment makes this possible + HEAP32[((start + attribute.offset + bytes*j)>>2) + k] = HEAP32[(attribute.pointer>>2) + j*size4 + k]; + } + } + attribute.pointer = start + attribute.offset; + } + } else { + // normal situation, everything is strided and in the same buffer + for (var i = 0; i < attributes.length; i++) { + var attribute = attributes[i]; + if (!attribute) break; + attribute.offset = attribute.pointer - start; + if (attribute.offset > bytes) { // ensure we start where we should + assert((attribute.offset - bytes)%4 == 0); // XXX assuming 4-alignment + bytes += attribute.offset - bytes; + } + bytes += attribute.size * GL.byteSizeByType[attribute.type - GL.byteSizeByTypeRoot]; + if (bytes % 4 != 0) bytes += 4 - (bytes % 4); // XXX assuming 4-alignment + } + assert(beginEnd || bytes <= stride); // if not begin-end, explicit stride should make sense with total byte size + if (bytes < stride) { // ensure the size is that of the stride + bytes = stride; } - bytes += attribute.size * GL.immediate.byteSizeByType[attribute.type - GL.immediate.byteSizeByTypeRoot]; - if (bytes % 4 != 0) bytes += 4 - (bytes % 4); // XXX assuming 4-alignment - } - assert(stride == 0 || bytes <= stride); - if (bytes < stride) { // ensure the size is that of the stride - bytes = stride; } GL.immediate.stride = bytes; @@ -2026,6 +2292,9 @@ var LibraryGL = { }, flush: function(numProvidedIndexes, startIndex, ptr) { +#if ASSERTIONS + assert(numProvidedIndexes >= 0 || !numProvidedIndexes); +#endif startIndex = startIndex || 0; ptr = ptr || 0; @@ -2053,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; @@ -2069,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; } @@ -2085,6 +2354,10 @@ var LibraryGL = { if (emulatedElementArrayBuffer) { Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, GL.buffers[GL.currElementArrayBuffer] || null); } + +#if GL_UNSAFE_OPTS == 0 + renderer.cleanup(); +#endif } }, @@ -2120,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); }, @@ -2134,8 +2407,10 @@ var LibraryGL = { glVertex2fv: function(p) { _glVertex3f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}, 0); }, + + glVertex3i: 'glVertex3f', - glVertex2i: function() { throw 'glVertex2i: TODO' }, + glVertex2i: 'glVertex3f', glTexCoord2i: function(u, v) { #if ASSERTIONS @@ -2299,25 +2574,21 @@ var LibraryGL = { // ClientState/gl*Pointer glEnableClientState: function(cap, disable) { - var attrib; - switch(cap) { - case 0x8078: // GL_TEXTURE_COORD_ARRAY - attrib = GL.immediate.TEXTURE0 + GL.immediate.clientActiveTexture; break; - case 0x8074: // GL_VERTEX_ARRAY - attrib = GL.immediate.VERTEX; break; - case 0x8075: // GL_NORMAL_ARRAY - attrib = GL.immediate.NORMAL; break; - case 0x8076: // GL_COLOR_ARRAY - attrib = GL.immediate.COLOR; break; - default: - throw 'unhandled clientstate: ' + cap; + var attrib = GLEmulation.getAttributeFromCapability(cap); + if (attrib === null) { +#if ASSERTIONS + Module.printErr('WARNING: unhandled clientstate: ' + cap); +#endif + return; } if (disable && GL.immediate.enabledClientAttributes[attrib]) { GL.immediate.enabledClientAttributes[attrib] = false; GL.immediate.totalEnabledClientAttributes--; + if (GLEmulation.currentVao) delete GLEmulation.currentVao.enabledClientStates[cap]; } else if (!disable && !GL.immediate.enabledClientAttributes[attrib]) { GL.immediate.enabledClientAttributes[attrib] = true; GL.immediate.totalEnabledClientAttributes++; + if (GLEmulation.currentVao) GLEmulation.currentVao.enabledClientStates[cap] = 1; } GL.immediate.modifiedClientAttributes = true; }, @@ -2344,6 +2615,63 @@ var LibraryGL = { GL.immediate.clientActiveTexture = texture - 0x84C0; // GL_TEXTURE0 }, + // Vertex array object (VAO) support. TODO: when the WebGL extension is popular, use that and remove this code and GL.vaos + glGenVertexArrays__deps: ['$GLEMulation'], + glGenVertexArrays__sig: ['vii'], + glGenVertexArrays: function(n, vaos) { + for (var i = 0; i < n; i++) { + var id = GL.getNewId(GLEmulation.vaos); + GLEmulation.vaos[id] = { + id: id, + arrayBuffer: 0, + elementArrayBuffer: 0, + enabledVertexAttribArrays: {}, + vertexAttribPointers: {}, + enabledClientStates: {}, + }; + {{{ makeSetValue('vaos', 'i*4', 'id', 'i32') }}}; + } + }, + glDeleteVertexArrays__sig: ['vii'], + glDeleteVertexArrays: function(n, vaos) { + for (var i = 0; i < n; i++) { + var id = {{{ makeGetValue('vaos', 'i*4', 'i32') }}}; + GLEmulation.vaos[id] = null; + if (GLEmulation.currentVao && GLEmulation.currentVao.id == id) GLEmulation.currentVao = null; + } + }, + glBindVertexArray__sig: ['vi'], + glBindVertexArray: function(vao) { + // undo vao-related things, wipe the slate clean, both for vao of 0 or an actual vao + GLEmulation.currentVao = null; // make sure the commands we run here are not recorded + if (GL.immediate.lastRenderer) GL.immediate.lastRenderer.cleanup(); + _glBindBuffer(Module.ctx.ARRAY_BUFFER, 0); // XXX if one was there before we were bound? + _glBindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, 0); + for (var vaa in GLEmulation.enabledVertexAttribArrays) { + Module.ctx.disableVertexAttribArray(vaa); + } + GLEmulation.enabledVertexAttribArrays = {}; + GL.immediate.enabledClientAttributes = [0, 0]; + GL.immediate.totalEnabledClientAttributes = 0; + GL.immediate.modifiedClientAttributes = true; + if (vao) { + // replay vao + var info = GLEmulation.vaos[vao]; + _glBindBuffer(Module.ctx.ARRAY_BUFFER, info.arrayBuffer); // XXX overwrite current binding? + _glBindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, info.elementArrayBuffer); + for (var vaa in info.enabledVertexAttribArrays) { + _glEnableVertexAttribArray(vaa); + } + for (var vaa in info.vertexAttribPointers) { + _glVertexAttribPointer.apply(null, info.vertexAttribPointers[vaa]); + } + for (var attrib in info.enabledClientStates) { + _glEnableClientState(attrib|0); + } + GLEmulation.currentVao = info; // set currentVao last, so the commands we ran here were not recorded + } + }, + // OpenGL Immediate Mode matrix routines. // Note that in the future we might make these available only in certain modes. glMatrixMode__deps: ['$GL', '$GLImmediateSetup', '$GLEmulation'], // emulation is not strictly needed, this is a workaround @@ -2434,6 +2762,7 @@ var LibraryGL = { GL.immediate.matrix.lib.mat4.multiply(GL.immediate.matrix[GL.immediate.currentMatrix], GL.immediate.matrix.lib.mat4.frustum(left, right, bottom, top_, nearVal, farVal)); }, + glFrustumf: 'glFrustum', glOrtho: function(left, right, bottom, top_, nearVal, farVal) { GL.immediate.matricesModified = true; @@ -2464,8 +2793,9 @@ var LibraryGL = { gluPerspective: function(fov, aspect, near, far) { GL.immediate.matricesModified = true; - GL.immediate.matrix.lib.mat4.multiply(GL.immediate.matrix[GL.immediate.currentMatrix], - GL.immediate.matrix.lib.mat4.perspective(fov, aspect, near, far, GL.immediate.currentMatrix)); + GL.immediate.matrix[GL.immediate.currentMatrix] = + GL.immediate.matrix.lib.mat4.perspective(fov, aspect, near, far, + GL.immediate.matrix[GL.immediate.currentMatrix]); }, gluLookAt: function(ex, ey, ez, cx, cy, cz, ux, uy, uz) { @@ -2534,36 +2864,140 @@ var LibraryGL = { glTexGeni: function() { throw 'glTexGeni: TODO' }, glTexGenfv: function() { throw 'glTexGenfv: TODO' }, - glTexEnvi: function() { throw 'glTexEnvi: TODO' }, - glTexEnvfv: function() { throw 'glTexEnvfv: TODO' }, + glTexEnvi: function() { Runtime.warnOnce('glTexEnvi: TODO') }, + glTexEnvfv: function() { Runtime.warnOnce('glTexEnvfv: TODO') }, glTexImage1D: function() { throw 'glTexImage1D: TODO' }, glTexCoord3f: function() { throw 'glTexCoord3f: TODO' }, glGetTexLevelParameteriv: function() { throw 'glGetTexLevelParameteriv: TODO' }, - // signatures of simple pass-through functions, see later - glActiveTexture__sig: 'vi', + 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) { + cb.size = size; + cb.type = type; + cb.normalized = normalized; + cb.stride = stride; + cb.ptr = ptr; + return; + } + cb.enabled = false; +#endif + Module.ctx.vertexAttribPointer(index, size, type, normalized, stride, ptr); + }, + glEnableVertexAttribArray__sig: 'vi', + glEnableVertexAttribArray: function(index) { +#if FULL_ES2 + var cb = GL.clientBuffers[index]; +#if ASSERTIONS + assert(cb, index); +#endif + cb.enabled = true; +#endif + Module.ctx.enableVertexAttribArray(index); + }, + glDisableVertexAttribArray__sig: 'vi', - glVertexAttribPointer__sig: 'viiiiii', + glDisableVertexAttribArray: function(index) { +#if FULL_ES2 + var cb = GL.clientBuffers[index]; +#if ASSERTIONS + assert(cb, index); +#endif + cb.enabled = false; +#endif + Module.ctx.disableVertexAttribArray(index); + }, + + glDrawArrays: function(mode, first, count) { +#if FULL_ES2 + // bind any client-side buffers + GL.preDrawHandleClientVertexAttribBindings(first + count); +#endif + + Module.ctx.drawArrays(mode, first, count); + +#if FULL_ES2 + GL.postDrawHandleClientVertexAttribBindings(); +#endif + }, + + glDrawElements: function(mode, count, type, indices) { +#if FULL_ES2 + var buf; + if (!GL.currElementArrayBuffer) { + 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 + size), + Module.ctx.DYNAMIC_DRAW); + // the index is now 0 + indices = 0; + } + + // bind any client-side buffers + GL.preDrawHandleClientVertexAttribBindings(count); +#endif + + Module.ctx.drawElements(mode, count, type, indices); + +#if FULL_ES2 + GL.postDrawHandleClientVertexAttribBindings(count); + + if (!GL.currElementArrayBuffer) { + Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, null); + } +#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', + glBindRenderbufferOES : 'glBindRenderbuffer', + glGetRenderbufferParameterivOES : 'glGetRenderbufferParameteriv', + glFramebufferRenderbufferOES : 'glFramebufferRenderbuffer', + glRenderbufferStorageOES : 'glRenderbufferStorage', + glCheckFramebufferStatusOES : 'glCheckFramebufferStatus', + glDeleteFramebuffersOES : 'glDeleteFramebuffers', + glDeleteRenderbuffersOES : 'glDeleteRenderbuffers', + glGenVertexArraysOES: 'glGenVertexArrays', + glDeleteVertexArraysOES: 'glDeleteVertexArrays', + glBindVertexArrayOES: 'glBindVertexArray', + glFramebufferTexture2DOES: 'glFramebufferTexture2D' }; // Simple pass-through functions. Starred ones have return values. [X] ones have X in the C name but not in the JS name -[[0, 'shadeModel getError* finish flush'], - [1, 'clearDepth clearDepth[f] depthFunc enable disable frontFace cullFace clear enableVertexAttribArray disableVertexAttribArray lineWidth clearStencil depthMask stencilMask checkFramebufferStatus* generateMipmap activeTexture blendEquation sampleCoverage isEnabled*'], +[[0, 'getError* finish flush'], + [1, 'clearDepth clearDepth[f] depthFunc enable disable frontFace cullFace clear lineWidth clearStencil depthMask stencilMask checkFramebufferStatus* generateMipmap activeTexture blendEquation sampleCoverage isEnabled*'], [2, 'blendFunc blendEquationSeparate depthRange depthRange[f] stencilMaskSeparate hint polygonOffset'], - [3, 'texParameteri texParameterf drawArrays vertexAttrib2f stencilFunc stencilOp'], - [4, 'viewport clearColor scissor vertexAttrib3f colorMask drawElements renderbufferStorage blendFuncSeparate blendColor stencilFuncSeparate stencilOpSeparate'], + [3, 'texParameteri texParameterf vertexAttrib2f stencilFunc stencilOp'], + [4, 'viewport clearColor scissor vertexAttrib3f colorMask renderbufferStorage blendFuncSeparate blendColor stencilFuncSeparate stencilOpSeparate'], [5, 'vertexAttrib4f'], - [6, 'vertexAttribPointer'], [8, 'copyTexImage2D copyTexSubImage2D']].forEach(function(data) { var num = data[0]; var names = data[1]; var args = range(num).map(function(i) { return 'x' + i }).join(', '); - var plainStub = '(function(' + args + ') { ' + (num > 0 ? 'Module.ctx.NAME(' + args + ')' : '') + ' })'; - var returnStub = '(function(' + args + ') { ' + (num > 0 ? 'return Module.ctx.NAME(' + args + ')' : '') + ' })'; + var plainStub = '(function(' + args + ') { Module.ctx.NAME(' + args + ') })'; + var returnStub = '(function(' + args + ') { return Module.ctx.NAME(' + args + ') })'; names.split(' ').forEach(function(name) { var stub = plainStub; if (name[name.length-1] == '*') { @@ -2596,5 +3030,10 @@ LibraryGL.$GLEmulation__deps.push(function() { for (var func in Functions.getIndex.tentative) Functions.getIndex(func); }); +if (FORCE_GL_EMULATION) { + LibraryGL.glDrawElements__deps = LibraryGL.glDrawElements__deps.concat('$GLEmulation'); + LibraryGL.glDrawArrays__deps = LibraryGL.glDrawArrays__deps.concat('$GLEmulation'); +} + mergeInto(LibraryManager.library, LibraryGL); diff --git a/src/library_glut.js b/src/library_glut.js index 2e662698..bb4dfefa 100644 --- a/src/library_glut.js +++ b/src/library_glut.js @@ -381,6 +381,12 @@ var LibraryGLUT = { return 1; }, + glutDestroyWindow__deps: ['$Browser'], + glutDestroyWindow: function(name) { + Module.ctx = Browser.destroyContext(Module['canvas'], true, true); + return 1; + }, + glutReshapeWindow__deps: ['$GLUT', 'glutPostRedisplay'], glutReshapeWindow: function(width, height) { GLUT.cancelFullScreen(); diff --git a/src/library_sdl.js b/src/library_sdl.js index cfab6410..5033b27e 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -44,6 +44,8 @@ var LibrarySDL = { ctrlKey: false, altKey: false, + textInput: false, + startTime: null, mouseX: 0, mouseY: 0, @@ -173,6 +175,11 @@ var LibrarySDL = { ['i16', 'mod'], ['i32', 'unicode'] ]), + TextInputEvent: Runtime.generateStructInfo([ + ['i32', 'type'], + ['i32', 'windowID'], + ['b256', 'text'], + ]), MouseMotionEvent: Runtime.generateStructInfo([ ['i32', 'type'], ['i32', 'windowID'], @@ -373,7 +380,7 @@ var LibrarySDL = { } } // fall through - case 'keydown': case 'keyup': case 'mousedown': case 'mouseup': case 'DOMMouseScroll': case 'mousewheel': + case 'keydown': case 'keyup': case 'keypress': case 'mousedown': case 'mouseup': case 'DOMMouseScroll': case 'mousewheel': if (event.type == 'DOMMouseScroll' || event.type == 'mousewheel') { var button = (event.type == 'DOMMouseScroll' ? event.detail : -event.wheelDelta) > 0 ? 4 : 3; var event2 = { @@ -397,6 +404,10 @@ var LibrarySDL = { SDL.DOMButtons[event.button] = 0; } + if (event.type == 'keypress' && !SDL.textInput) { + break; + } + SDL.events.push(event); if (SDL.events.length >= 10000) { Module.printErr('SDL event queue full, dropping earliest event'); @@ -476,6 +487,15 @@ var LibrarySDL = { break; } + case 'keypress': { + {{{ makeSetValue('ptr', 'SDL.structs.TextInputEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}} + // Not filling in windowID for now + var cStr = intArrayFromString(String.fromCharCode(event.charCode)); + for (var i = 0; i < cStr.length; ++i) { + {{{ makeSetValue('ptr', 'SDL.structs.TextInputEvent.text + i', 'cStr[i]', 'i8') }}}; + } + break; + } case 'mousedown': case 'mouseup': if (event.type == 'mousedown') { // SDL_BUTTON(x) is defined as (1 << ((x)-1)). SDL buttons are 1-3, @@ -607,15 +627,18 @@ var LibrarySDL = { SDL_Init: function(what) { SDL.startTime = Date.now(); // capture all key events. we just keep down and up, but also capture press to prevent default actions - document.onkeydown = SDL.receiveEvent; - document.onkeyup = SDL.receiveEvent; - document.onkeypress = SDL.receiveEvent; + if (!Module['doNotCaptureKeyboard']) { + document.onkeydown = SDL.receiveEvent; + document.onkeyup = SDL.receiveEvent; + document.onkeypress = SDL.receiveEvent; + } window.onunload = SDL.receiveEvent; SDL.keyboardState = _malloc(0x10000); _memset(SDL.keyboardState, 0, 0x10000); // Initialize this structure carefully for closure SDL.DOMEventToSDLEvent['keydown'] = 0x300 /* SDL_KEYDOWN */; SDL.DOMEventToSDLEvent['keyup'] = 0x301 /* SDL_KEYUP */; + SDL.DOMEventToSDLEvent['keypress'] = 0x303 /* SDL_TEXTINPUT */; SDL.DOMEventToSDLEvent['mousedown'] = 0x401 /* SDL_MOUSEBUTTONDOWN */; SDL.DOMEventToSDLEvent['mouseup'] = 0x402 /* SDL_MOUSEBUTTONUP */; SDL.DOMEventToSDLEvent['mousemove'] = 0x400 /* SDL_MOUSEMOTION */; @@ -638,8 +661,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; }, @@ -896,7 +919,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() { @@ -1052,6 +1094,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 @@ -1077,7 +1128,9 @@ var LibrarySDL = { } var surf = SDL.makeSurface(raw.width, raw.height, 0, false, 'load:' + filename); var surfData = SDL.surfaces[surf]; + surfData.ctx.globalCompositeOperation = "copy"; surfData.ctx.drawImage(raw, 0, 0, raw.width, raw.height, 0, 0, raw.width, raw.height); + surfData.ctx.globalCompositeOperation = "source-over"; // XXX SDL does not specify that loaded images must have available pixel data, in fact // there are cases where you just want to blit them, so you just need the hardware // accelerated version. However, code everywhere seems to assume that the pixels @@ -1171,11 +1224,21 @@ var LibrarySDL = { SDL_CondWait: function() {}, SDL_DestroyCond: function() {}, - SDL_StartTextInput: function() {}, // TODO - SDL_StopTextInput: function() {}, // TODO + SDL_StartTextInput: function() { + SDL.textInput = true; + }, + SDL_StopTextInput: function() { + SDL.textInput = false; + }, // SDL Mixer + Mix_Init: function(flags) { + if (!flags) return 0; + return 8; /* MIX_INIT_OGG */ + }, + Mix_Quit: function(){}, + Mix_OpenAudio: function(frequency, format, channels, chunksize) { SDL.allocateChannels(32); // Just record the values for a later call to Mix_QuickLoad_RAW @@ -1280,6 +1343,8 @@ var LibrarySDL = { // the browser has already preloaded the audio file. var channelInfo = SDL.channels[channel]; channelInfo.audio = audio = audio.cloneNode(true); + audio.numChannels = info.audio.numChannels; + audio.frequency = info.audio.frequency; if (SDL.channelFinished) { audio['onended'] = function() { // TODO: cache these Runtime.getFuncWrapper(SDL.channelFinished, 'vi')(channel); @@ -1426,10 +1491,70 @@ 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(id) { + if (id === -1) { + var count = 0; + for (var i = 0; i < SDL.audios.length; i++) { + count += SDL.Mix_Playing(i); + } + return count; + } + var info = SDL.audios[id]; + if (info && info.audio && !info.audio.paused) { + return 1; + } + return 0; + }, + + Mix_Pause: function(id) { + if (id === -1) { + for (var i = 0; i<SDL.audios.length;i++) { + SDL.Mix_Pause(i); + } + return; + } + var info = SDL.audios[id]; + if (info && info.audio) { + info.audio.pause(); + } + }, + + // http://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer_39.html#SEC39 + Mix_Paused: function(id) { + if (id === -1) { + var pausedCount = 0; + for (var i = 0; i<SDL.audios.length;i++) { + pausedCount += SDL.Mix_Paused(i); + } + return pausedCount; + } + var info = SDL.audios[id]; + 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(id) { + if (id === -1) { + for (var i = 0; i<SDL.audios.length;i++) { + SDL.Mix_Resume(i); + } + return; + } + var info = SDL.audios[id]; + 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..c3651bd9 100644 --- a/src/long.js +++ b/src/long.js @@ -1551,6 +1551,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 5b48b305..797d4d83 100644 --- a/src/modules.js +++ b/src/modules.js @@ -180,7 +180,16 @@ var Variables = { globals: {}, indexedGlobals: {}, // for indexed globals, ident ==> index // Used in calculation of indexed globals - nextIndexedOffset: 0 + nextIndexedOffset: 0, + + resolveAliasToIdent: function(ident) { + while (1) { + var varData = Variables.globals[ident]; + if (!(varData && varData.targetIdent)) break; + ident = varData.targetIdent; // might need to eval to turn (6) into 6 + } + return ident; + }, }; var Types = { @@ -331,12 +340,23 @@ var Functions = { } } } + if (table.length > 20) { + // add some newlines in the table, for readability + var j = 10; + while (j+10 < table.length) { + table[j] += '\n'; + j += 10; + } + } var indices = table.toString().replace('"', ''); if (BUILD_AS_SHARED_LIB) { // Shared libraries reuse the parent's function table. tables[t] = Functions.getTable(t) + '.push.apply(' + Functions.getTable(t) + ', [' + indices + ']);\n'; } else { tables[t] = 'var ' + Functions.getTable(t) + ' = [' + indices + '];\n'; + if (SAFE_DYNCALLS) { + tables[t] += 'var FUNCTION_TABLE_NAMES = ' + JSON.stringify(table).replace(/\n/g, '').replace(/,0/g, ',0\n') + ';\n'; + } } } if (!generated && !ASM_JS) { diff --git a/src/parseTools.js b/src/parseTools.js index a076c862..48274cd5 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -78,6 +78,7 @@ function toNiceIdent(ident) { assert(ident); if (parseFloat(ident) == ident) return ident; if (ident == 'null') return '0'; // see parseNumerical + if (ident == 'undef') return '0'; return ident.replace('%', '$').replace(/["&\\ \.@:<>,\*\[\]\(\)-]/g, '_'); } @@ -103,6 +104,11 @@ function isNiceIdent(ident, loose) { } } +function isJSVar(ident) { + return /^\(?[$_]?[\w$_\d ]*\)+$/.test(ident); + +} + function isStructPointerType(type) { // This test is necessary for clang - in llvm-gcc, we // could check for %struct. The downside is that %1 can @@ -687,7 +693,7 @@ function makeCopyI64(value) { function parseArbitraryInt(str, bits) { // We parse the string into a vector of digits, base 10. This is convenient to work on. - assert(bits % 32 == 0 || ('i' + (bits % 32)) in Runtime.INT_TYPES, 'Arbitrary-sized ints must tails that are of legal size'); + assert(bits > 0); // NB: we don't check that the value in str can fit in this amount of bits function str2vec(s) { // index 0 is the highest value var ret = []; @@ -977,27 +983,27 @@ function checkSafeHeap() { return SAFE_HEAP === 1 || checkSpecificSafeHeap(); } -if (ASM_JS) { - var hexMemoryMask = '0x' + (TOTAL_MEMORY-1).toString(16); - var decMemoryMask = (TOTAL_MEMORY-1).toString(); - var memoryMask = hexMemoryMask.length <= decMemoryMask.length ? hexMemoryMask : decMemoryMask; -} - function getHeapOffset(offset, type, forceAsm) { if (USE_TYPED_ARRAYS !== 2) { return offset; - } else { - if (Runtime.getNativeFieldSize(type) > 4) { - type = 'i32'; // XXX we emulate 64-bit values as 32 - } - var shifts = Math.log(Runtime.getNativeTypeSize(type))/Math.LN2; - offset = '(' + offset + ')'; - if (ASM_JS && (phase == 'funcs' || forceAsm)) offset = '(' + offset + '&' + memoryMask + ')'; - if (shifts != 0) { - return '(' + offset + '>>' + shifts + ')'; + } + + if (Runtime.getNativeFieldSize(type) > 4) { + type = 'i32'; // XXX we emulate 64-bit values as 32 + } + + var sz = Runtime.getNativeTypeSize(type); + var shifts = Math.log(sz)/Math.LN2; + offset = '(' + offset + ')'; + if (shifts != 0) { + if (CHECK_HEAP_ALIGN) { + return '(CHECK_ALIGN_' + sz + '(' + offset + ')>>' + shifts + ')'; } else { - return offset; + return '(' + offset + '>>' + shifts + ')'; } + } else { + // we need to guard against overflows here, HEAP[U]8 expects a guaranteed int + return isJSVar(offset) ? offset : '(' + offset + '|0)'; } } @@ -1046,20 +1052,6 @@ function asmCoercion(value, type, signedness) { } } -var TWO_TWENTY = Math.pow(2, 20); - -function asmMultiplyI32(a, b) { - // special-case: there is no integer multiply in asm, because there is no true integer - // multiply in JS. While we wait for Math.imul, do double multiply - if ((isNumber(a) && Math.abs(a) < TWO_TWENTY) || (isNumber(b) && Math.abs(b) < TWO_TWENTY)) { - return '(((' + a + ')*(' + b + '))&-1)'; // small enough to emit directly as a multiply - } - if (USE_MATH_IMUL) { - return 'Math.imul(' + a + ',' + b + ')'; - } - return '(~~(+((' + a + ')|0) * +((' + b + ')|0)))'; -} - function asmFloatToInt(x) { return '(~~(' + x + '))'; } @@ -1071,7 +1063,6 @@ function makeGetTempDouble(i, type, forSet) { // get an aliased part of the temp var ptr = getFastValue('tempDoublePtr', '+', Runtime.getNativeTypeSize(type)*i); var offset; if (type == 'double') { - if (ASM_JS) ptr = '(' + ptr + ')&' + memoryMask; offset = '(' + ptr + ')>>3'; } else { offset = getHeapOffset(ptr, type); @@ -1154,8 +1145,8 @@ function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSa } } -function makeGetValueAsm(ptr, pos, type) { - return makeGetValue(ptr, pos, type, null, null, null, null, null, true); +function makeGetValueAsm(ptr, pos, type, unsigned) { + return makeGetValue(ptr, pos, type, null, unsigned, null, null, null, true); } function indexizeFunctions(value, type) { @@ -1258,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) { @@ -1278,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); @@ -1293,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; } @@ -1336,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); @@ -1344,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; @@ -1373,17 +1348,23 @@ function makeHEAPView(which, start, end) { var PLUS_MUL = set('+', '*'); var MUL_DIV = set('*', '/'); var PLUS_MINUS = set('+', '-'); +var TWO_TWENTY = Math.pow(2, 20); // Given two values and an operation, returns the result of that operation. // Tries to do as much as possible at compile time. +// Leaves overflows etc. unhandled, *except* for integer multiply, in order to be efficient with Math.imul function getFastValue(a, op, b, type) { a = a.toString(); b = b.toString(); + a = a == 'true' ? '1' : (a == 'false' ? '0' : a); + b = b == 'true' ? '1' : (b == 'false' ? '0' : b); if (isNumber(a) && isNumber(b)) { if (op == 'pow') { return Math.pow(a, b).toString(); } else { - return eval(a + op + '(' + b + ')').toString(); // parens protect us from "5 - -12" being seen as "5--12" which is "(5--)12" + var value = eval(a + op + '(' + b + ')'); // parens protect us from "5 - -12" being seen as "5--12" which is "(5--)12" + if (op == '/' && type in Runtime.INT_TYPES) value = value|0; // avoid emitting floats + return value.toString(); } } if (op == 'pow') { @@ -1411,8 +1392,14 @@ function getFastValue(a, op, b, type) { return '(' + a + '<<' + shifts + ')'; } } - if (ASM_JS && !(type in Runtime.FLOAT_TYPES)) { - return asmMultiplyI32(a, b); // unoptimized multiply, do it using asm.js's special multiply operation + if (!(type in Runtime.FLOAT_TYPES)) { + // if guaranteed small enough to not overflow into a double, do a normal multiply + var bits = getBits(type) || 32; // default is 32-bit multiply for things like getelementptr indexes + // Note that we can emit simple multiple in non-asm.js mode, but asm.js will not parse "16-bit" multiple, so must do imul there + if ((isNumber(a) && Math.abs(a) < TWO_TWENTY) || (isNumber(b) && Math.abs(b) < TWO_TWENTY) || (bits < 32 && !ASM_JS)) { + return '(((' + a + ')*(' + b + '))&' + ((Math.pow(2, bits)-1)|0) + ')'; // keep a non-eliminatable coercion directly on this + } + return 'Math.imul(' + a + ',' + b + ')'; } } else { if (a == '0') { @@ -1557,7 +1544,7 @@ function makePointer(slab, pos, allocator, type, ptr) { var ret = ''; var index = 0; while (index < array.length) { - ret = (ret ? ret + '.concat(' : '') + '[' + array.slice(index, index + chunkSize).map(JSON.stringify) + ']' + (ret ? ')' : ''); + ret = (ret ? ret + '.concat(' : '') + '[' + array.slice(index, index + chunkSize).map(JSON.stringify) + ']' + (ret ? ')\n' : ''); index += chunkSize; } return ret; @@ -1718,9 +1705,7 @@ function handleOverflow(text, bits) { if (!bits) return text; var correct = correctOverflows(); warnOnce(!correct || bits <= 32, 'Cannot correct overflows of this many bits: ' + bits); - if (CHECK_OVERFLOWS) return 'CHECK_OVERFLOW(' + text + ', ' + bits + ', ' + Math.floor(correctSpecificOverflow() && !PGO) + ( - PGO ? ', "' + Debugging.getIdentifier() + '"' : '' - ) + ')'; + if (CHECK_OVERFLOWS) return 'CHECK_OVERFLOW(' + text + ', ' + bits + ', ' + Math.floor(correctSpecificOverflow()) + ')'; if (!correct) return text; if (bits == 32) { return '((' + text + ')|0)'; @@ -1828,9 +1813,7 @@ function makeSignOp(value, type, op, force, ignore) { var bits, full; if (type in Runtime.INT_TYPES) { bits = parseInt(type.substr(1)); - full = op + 'Sign(' + value + ', ' + bits + ', ' + Math.floor(ignore || (correctSpecificSign() && !PGO)) + ( - PGO ? ', "' + (ignore ? '' : Debugging.getIdentifier()) + '"' : '' - ) + ')'; + full = op + 'Sign(' + value + ', ' + bits + ', ' + Math.floor(ignore || (correctSpecificSign())) + ')'; // Always sign/unsign constants at compile time, regardless of CHECK/CORRECT if (isNumber(value)) { return eval(full).toString(); @@ -1842,9 +1825,10 @@ function makeSignOp(value, type, op, force, ignore) { if (!CHECK_SIGNS || ignore) { if (bits === 32) { if (op === 're') { - return '((' + value + ')|0)'; + return '(' + getFastValue(value, '|', '0') + ')'; } else { - return '((' + value + ')>>>0)'; + + return '(' + getFastValue(value, '>>>', '0') + ')'; // Alternatively, we can consider the lengthier // return makeInlineCalculation('VALUE >= 0 ? VALUE : ' + Math.pow(2, bits) + ' + VALUE', value, 'tempBigInt'); // which does not always turn us into a 32-bit *un*signed value @@ -1853,7 +1837,7 @@ function makeSignOp(value, type, op, force, ignore) { if (op === 're') { return makeInlineCalculation('(VALUE << ' + (32-bits) + ') >> ' + (32-bits), value, 'tempInt'); } else { - return '((' + value + ')&' + (Math.pow(2, bits)-1) + ')'; + return '(' + getFastValue(value, '&', Math.pow(2, bits)-1) + ')'; } } else { // bits > 32 if (op === 're') { @@ -2143,14 +2127,7 @@ function processMathop(item) { case 'add': return handleOverflow(getFastValue(idents[0], '+', idents[1], item.type), bits); case 'sub': return handleOverflow(getFastValue(idents[0], '-', idents[1], item.type), bits); case 'sdiv': case 'udiv': return makeRounding(getFastValue(idents[0], '/', idents[1], item.type), bits, op[0] === 's'); - case 'mul': { - if (bits == 32 && PRECISE_I32_MUL) { - Types.preciseI64MathUsed = true; - return '(i64Math' + (ASM_JS ? '_' : '.') + 'multiply(' + asmCoercion(idents[0], 'i32') + ',0,' + asmCoercion(idents[1], 'i32') + ',0),' + makeGetValue('tempDoublePtr', 0, 'i32') + ')'; - } else { - return '((' +getFastValue(idents[0], '*', idents[1], item.type) + ')&-1)'; // force a non-eliminatable coercion here, to prevent a double result from leaking - } - } + case 'mul': return getFastValue(idents[0], '*', idents[1], item.type); // overflow handling is already done in getFastValue for '*' case 'urem': case 'srem': return getFastValue(idents[0], '%', idents[1], item.type); case 'or': { if (bits > 32) { @@ -2211,7 +2188,6 @@ function processMathop(item) { case 'ne': case 'eq': { // We must sign them, so we do not compare -1 to 255 (could have unsigned them both too) // since LLVM tells us if <=, >= etc. comparisons are signed, but not == and !=. - assert(paramTypes[0] == paramTypes[1]); idents[0] = makeSignOp(idents[0], paramTypes[0], 're'); idents[1] = makeSignOp(idents[1], paramTypes[1], 're'); return idents[0] + (variant === 'eq' ? '==' : '!=') + idents[1]; diff --git a/src/preamble.js b/src/preamble.js index aab50e9a..9bc68d8f 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -150,51 +150,24 @@ function SAFE_HEAP_COPY_HISTORY(dest, src) { //========================================== #endif -var CorrectionsMonitor = { -#if PGO - MAX_ALLOWED: Infinity, -#else - MAX_ALLOWED: 0, // XXX +#if CHECK_HEAP_ALIGN +//======================================== +// Debugging tools - alignment check +//======================================== +function CHECK_ALIGN_8(addr) { + assert((addr & 7) == 0, "address must be 8-byte aligned, is " + addr + "!"); + return addr; +} +function CHECK_ALIGN_4(addr) { + assert((addr & 3) == 0, "address must be 4-byte aligned, is " + addr + "!"); + return addr; +} +function CHECK_ALIGN_2(addr) { + assert((addr & 1) == 0, "address must be 2-byte aligned!"); + return addr; +} #endif - corrections: 0, - sigs: {}, - note: function(type, succeed, sig) { - if (!succeed) { - this.corrections++; - if (this.corrections >= this.MAX_ALLOWED) abort('\n\nToo many corrections!'); - } -#if PGO - if (!sig) - sig = (new Error().stack).toString().split('\n')[2].split(':').slice(-1)[0]; // Spidermonkey-specific FIXME - sig = type + '|' + sig; - if (!this.sigs[sig]) { - //Module.print('Correction: ' + sig); - this.sigs[sig] = [0, 0]; // fail, succeed - } - this.sigs[sig][succeed ? 1 : 0]++; -#endif - }, - - print: function() { -#if PGO - var items = []; - for (var sig in this.sigs) { - items.push({ - sig: sig, - fails: this.sigs[sig][0], - succeeds: this.sigs[sig][1], - total: this.sigs[sig][0] + this.sigs[sig][1] - }); - } - items.sort(function(x, y) { return y.total - x.total; }); - for (var i = 0; i < items.length; i++) { - var item = items[i]; - Module.print(item.sig + ' : ' + item.total + ' hits, %' + (Math.ceil(100*item.fails/item.total)) + ' failures'); - } -#endif - } -}; #if CHECK_OVERFLOWS //======================================== @@ -207,24 +180,20 @@ function CHECK_OVERFLOW(value, bits, ignore, sig) { // For signedness issue here, see settings.js, CHECK_SIGNED_OVERFLOWS #if CHECK_SIGNED_OVERFLOWS if (value === Infinity || value === -Infinity || value >= twopbits1 || value < -twopbits1) { - CorrectionsMonitor.note('SignedOverflow', 0, sig); - if (value === Infinity || value === -Infinity || Math.abs(value) >= twopbits) CorrectionsMonitor.note('Overflow'); + throw 'SignedOverflow'; + if (value === Infinity || value === -Infinity || Math.abs(value) >= twopbits) throw 'Overflow'; + } #else if (value === Infinity || value === -Infinity || Math.abs(value) >= twopbits) { - CorrectionsMonitor.note('Overflow', 0, sig); + throw 'Overflow'; + } #endif #if CORRECT_OVERFLOWS - // Fail on >32 bits - we warned at compile time - if (bits <= 32) { - value = value & (twopbits - 1); - } -#endif - } else { -#if CHECK_SIGNED_OVERFLOWS - CorrectionsMonitor.note('SignedOverflow', 1, sig); -#endif - CorrectionsMonitor.note('Overflow', 1, sig); + // Fail on >32 bits - we warned at compile time + if (bits <= 32) { + value = value & (twopbits - 1); } +#endif return value; } #endif @@ -243,39 +212,6 @@ var INDENT = ''; var START_TIME = Date.now(); #endif -#if PROFILE -var PROFILING = 0; -var PROFILING_ROOT = { time: 0, children: {}, calls: 0 }; -var PROFILING_NODE; - -function startProfiling() { - PROFILING_NODE = PROFILING_ROOT; - PROFILING = 1; -} -Module['startProfiling'] = startProfiling; - -function stopProfiling() { - PROFILING = 0; - assert(PROFILING_NODE === PROFILING_ROOT, 'Must have popped all the profiling call stack'); -} -Module['stopProfiling'] = stopProfiling; - -function printProfiling() { - function dumpData(name_, node, indent) { - Module.print(indent + ('________' + node.time).substr(-8) + ': ' + name_ + ' (' + node.calls + ')'); - var children = []; - for (var child in node.children) { - children.push(node.children[child]); - children[children.length-1].name_ = child; - } - children.sort(function(x, y) { return y.time - x.time }); - children.forEach(function(child) { dumpData(child.name_, child, indent + ' ') }); - } - dumpData('root', PROFILING_ROOT, ' '); -} -Module['printProfiling'] = printProfiling; -#endif - //======================================== // Runtime essentials //======================================== @@ -334,11 +270,9 @@ Module["ccall"] = ccall; // Returns the C function with a specified identifier (for C++, you need to do manual name mangling) function getCFunc(ident) { try { - var func = eval('_' + ident); + var func = globalScope['Module']['_' + ident]; // closure exported function + if (!func) func = eval('_' + ident); // explicit lookup } catch(e) { - try { - func = globalScope['Module']['_' + ident]; // closure exported function - } catch(e) {} } assert(func, 'Cannot call unknown function ' + ident + ' (perhaps LLVM optimizations or closure removed it?)'); return func; @@ -478,14 +412,6 @@ Module['ALLOC_STACK'] = ALLOC_STACK; Module['ALLOC_STATIC'] = ALLOC_STATIC; Module['ALLOC_NONE'] = ALLOC_NONE; -// Simple unoptimized memset - necessary during startup -var _memset = function(ptr, value, num) { - var stop = ptr + num; - while (ptr < stop) { - {{{ makeSetValue('ptr++', 0, 'value', 'i8', null, true) }}}; - } -} - // allocate(): This is for internal use. You can use it yourself as well, but the interface // is a little tricky (see docs right below). The reason is that it is optimized // for multiple syntaxes to save space in generated code. So you should @@ -519,7 +445,18 @@ function allocate(slab, types, allocator, ptr) { } if (zeroinit) { - _memset(ret, 0, size); + var ptr = ret, stop; +#if USE_TYPED_ARRAYS == 2 + assert((ret & 3) == 0); + stop = ret + (size & ~3); + for (; ptr < stop; ptr += 4) { + {{{ makeSetValue('ptr', '0', '0', 'i32', null, true) }}}; + } +#endif + stop = ret + size; + while (ptr < stop) { + {{{ makeSetValue('ptr++', '0', '0', 'i8', null, true) }}}; + } return ret; } @@ -530,7 +467,7 @@ function allocate(slab, types, allocator, ptr) { } #endif - var i = 0, type; + var i = 0, type, typeSize, previousType; while (i < size) { var curr = slab[i]; @@ -552,7 +489,13 @@ function allocate(slab, types, allocator, ptr) { #endif setValue(ret+i, curr, type); - i += Runtime.getNativeTypeSize(type); + + // no need to look up size unless type changes, so cache it + if (previousType !== type) { + typeSize = Runtime.getNativeTypeSize(type); + previousType = type; + } + i += typeSize; } return ret; @@ -626,6 +569,7 @@ function enlargeMemory() { while (TOTAL_MEMORY <= STATICTOP) { // Simple heuristic. Override enlargeMemory() if your program has something more optimal for it TOTAL_MEMORY = alignMemoryPage(2*TOTAL_MEMORY); } + assert(TOTAL_MEMORY <= Math.pow(2, 30)); // 2^30==1GB is a practical maximum - 2^31 is already close to possible negative numbers etc. #if USE_TYPED_ARRAYS == 1 var oldIHEAP = IHEAP; Module['HEAP'] = Module['IHEAP'] = HEAP = IHEAP = new Int32Array(TOTAL_MEMORY); @@ -655,47 +599,43 @@ function enlargeMemory() { #endif var TOTAL_STACK = Module['TOTAL_STACK'] || {{{ TOTAL_STACK }}}; -#if ASM_JS == 0 var TOTAL_MEMORY = Module['TOTAL_MEMORY'] || {{{ TOTAL_MEMORY }}}; -#else -var TOTAL_MEMORY = {{{ TOTAL_MEMORY }}}; // in asm, we hardcode the mask, so cannot adjust memory at runtime -#endif var FAST_MEMORY = Module['FAST_MEMORY'] || {{{ FAST_MEMORY }}}; // Initialize the runtime's memory #if USE_TYPED_ARRAYS // check for full engine support (use string 'subarray' to avoid closure compiler confusion) - assert(!!Int32Array && !!Float64Array && !!(new Int32Array(1)['subarray']) && !!(new Int32Array(1)['set']), - 'Cannot fallback to non-typed array case: Code is too specialized'); +assert(!!Int32Array && !!Float64Array && !!(new Int32Array(1)['subarray']) && !!(new Int32Array(1)['set']), + 'Cannot fallback to non-typed array case: Code is too specialized'); #if USE_TYPED_ARRAYS == 1 - HEAP = IHEAP = new Int32Array(TOTAL_MEMORY); - IHEAPU = new Uint32Array(IHEAP.buffer); +HEAP = IHEAP = new Int32Array(TOTAL_MEMORY); +IHEAPU = new Uint32Array(IHEAP.buffer); #if USE_FHEAP - FHEAP = new Float64Array(TOTAL_MEMORY); +FHEAP = new Float64Array(TOTAL_MEMORY); #endif #endif #if USE_TYPED_ARRAYS == 2 - var buffer = new ArrayBuffer(TOTAL_MEMORY); - HEAP8 = new Int8Array(buffer); - HEAP16 = new Int16Array(buffer); - HEAP32 = new Int32Array(buffer); - HEAPU8 = new Uint8Array(buffer); - HEAPU16 = new Uint16Array(buffer); - HEAPU32 = new Uint32Array(buffer); - HEAPF32 = new Float32Array(buffer); - HEAPF64 = new Float64Array(buffer); - - // Endianness check (note: assumes compiler arch was little-endian) - HEAP32[0] = 255; - assert(HEAPU8[0] === 255 && HEAPU8[3] === 0, 'Typed arrays 2 must be run on a little-endian system'); +var buffer = new ArrayBuffer(TOTAL_MEMORY); +HEAP8 = new Int8Array(buffer); +HEAP16 = new Int16Array(buffer); +HEAP32 = new Int32Array(buffer); +HEAPU8 = new Uint8Array(buffer); +HEAPU16 = new Uint16Array(buffer); +HEAPU32 = new Uint32Array(buffer); +HEAPF32 = new Float32Array(buffer); +HEAPF64 = new Float64Array(buffer); + +// Endianness check (note: assumes compiler arch was little-endian) +HEAP32[0] = 255; +assert(HEAPU8[0] === 255 && HEAPU8[3] === 0, 'Typed arrays 2 must be run on a little-endian system'); #endif #else - // Make sure that our HEAP is implemented as a flat array. - HEAP = []; // Hinting at the size with |new Array(TOTAL_MEMORY)| should help in theory but makes v8 much slower - for (var i = 0; i < FAST_MEMORY; i++) { - HEAP[i] = 0; // XXX We do *not* use {{| makeSetValue(0, 'i', 0, 'null') |}} here, since this is done just to optimize runtime speed - } +// Make sure that our HEAP is implemented as a flat array. +HEAP = []; // Hinting at the size with |new Array(TOTAL_MEMORY)| should help in theory but makes v8 much slower +for (var i = 0; i < FAST_MEMORY; i++) { + HEAP[i] = 0; // XXX We do *not* use {{| makeSetValue(0, 'i', 0, 'null') |}} here, since this is done just to optimize runtime speed +} #endif Module['HEAP'] = HEAP; @@ -722,7 +662,7 @@ STACK_MAX = TOTAL_STACK; // we lose a little stack here, but TOTAL_STACK is nice #if USE_TYPED_ARRAYS == 2 var tempDoublePtr = Runtime.alignMemory(allocate(12, 'i8', ALLOC_STACK), 8); assert(tempDoublePtr % 8 == 0); -function copyTempFloat(ptr) { // functions, because inlining this code is increases code size too much +function copyTempFloat(ptr) { // functions, because inlining this code increases code size too much HEAP8[tempDoublePtr] = HEAP8[ptr]; HEAP8[tempDoublePtr+1] = HEAP8[ptr+1]; HEAP8[tempDoublePtr+2] = HEAP8[ptr+2]; @@ -773,9 +713,6 @@ function preMain() { } function exitRuntime() { callRuntimeCallbacks(__ATEXIT__); - - // Print summary of correction activity - CorrectionsMonitor.print(); } // Tools @@ -832,6 +769,20 @@ Module['writeArrayToMemory'] = writeArrayToMemory; {{{ unSign }}} {{{ reSign }}} +#if PRECISE_I32_MUL +if (!Math.imul) Math.imul = function(a, b) { + var ah = a >>> 16; + var al = a & 0xffff; + var bh = b >>> 16; + var bl = b & 0xffff; + return (al*bl + ((ah*bl + al*bh) << 16))|0; +}; +#else +Math.imul = function(a, b) { + return (a*b)|0; // fast but imprecise +}; +#endif + // A counter of dependencies for calling run(). If we need to // do asynchronous work before running, increment this and // decrement it. Incrementing must happen in a place like diff --git a/src/relooper/Relooper.cpp b/src/relooper/Relooper.cpp index ae8577b1..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 @@ -909,48 +921,54 @@ void Relooper::Calculate(Block *Entry) { } std::stack<Shape*> &LoopStack = *((std::stack<Shape*>*)Closure); - SHAPE_SWITCH(Root, { - MultipleShape *Fused = Shape::IsMultiple(Root->Next); - // If we are fusing a Multiple with a loop into this Simple, then visit it now - if (Fused && Fused->NeedLoop) { - LoopStack.push(Fused); - RECURSE_MULTIPLE_MANUAL(FindLabeledLoops, Fused); - } - 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) { - assert(LoopStack.size() > 0); - if (Details->Ancestor != LoopStack.top()) { - LabeledShape *Labeled = Shape::IsLabeled(Details->Ancestor); - Labeled->Labeled = true; - Details->Labeled = true; - } else { - Details->Labeled = false; + Shape *Next = Root; + while (Next) { + Root = Next; + Next = NULL; + + SHAPE_SWITCH(Root, { + MultipleShape *Fused = Shape::IsMultiple(Root->Next); + // If we are fusing a Multiple with a loop into this Simple, then visit it now + if (Fused && Fused->NeedLoop) { + LoopStack.push(Fused); + RECURSE_MULTIPLE_MANUAL(FindLabeledLoops, Fused); + } + 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) { + assert(LoopStack.size() > 0); + if (Details->Ancestor != LoopStack.top()) { + LabeledShape *Labeled = Shape::IsLabeled(Details->Ancestor); + Labeled->Labeled = true; + Details->Labeled = true; + } else { + Details->Labeled = false; + } } } - } - if (Fused && Fused->NeedLoop) { - LoopStack.pop(); - if (Fused->Next) FindLabeledLoops(Fused->Next); - } else { - if (Root->Next) FindLabeledLoops(Root->Next); - } - }, { - if (Multiple->NeedLoop) { - LoopStack.push(Multiple); - } - RECURSE_MULTIPLE(FindLabeledLoops); - if (Multiple->NeedLoop) { + if (Fused && Fused->NeedLoop) { + LoopStack.pop(); + Next = Fused->Next; + } else { + Next = Root->Next; + } + }, { + if (Multiple->NeedLoop) { + LoopStack.push(Multiple); + } + RECURSE_MULTIPLE(FindLabeledLoops); + if (Multiple->NeedLoop) { + LoopStack.pop(); + } + Next = Root->Next; + }, { + LoopStack.push(Loop); + RECURSE_LOOP(FindLabeledLoops); LoopStack.pop(); - } - if (Root->Next) FindLabeledLoops(Root->Next); - }, { - LoopStack.push(Loop); - RECURSE_LOOP(FindLabeledLoops); - LoopStack.pop(); - if (Root->Next) FindLabeledLoops(Root->Next); - }); + Next = Root->Next; + }); + } if (First) { delete (std::stack<Shape*>*)Closure; 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/test2.txt b/src/relooper/test2.txt index a847e806..c77ce491 100644 --- a/src/relooper/test2.txt +++ b/src/relooper/test2.txt @@ -1,11 +1,12 @@ ep -L1: -if (ep -> LBB1) { - LBB1 - if (!(LBB1 -> LBB2)) { - break L1; +do { + if (ep -> LBB1) { + LBB1 + if (!(LBB1 -> LBB2)) { + break; + } + LBB2 } - LBB2 -} +} while(0); LBB3 diff --git a/src/relooper/test3.txt b/src/relooper/test3.txt index 7d06f06a..696542ef 100644 --- a/src/relooper/test3.txt +++ b/src/relooper/test3.txt @@ -1,25 +1,27 @@ ep -L1: -if (ep -> LBB1) { - LBB1 - if (!(LBB1 -> LBB2)) { - break L1; +do { + if (ep -> LBB1) { + LBB1 + if (!(LBB1 -> LBB2)) { + break; + } + LBB2 } - LBB2 -} +} while(0); LBB3 -L5: -if (LBB3 -> LBB4) { - LBB4 - if (!(LBB4 -> LBB5)) { - break L5; - } - while(1) { - LBB5 - if (LBB5 -> LBB6) { - break L5; +L5: do { + if (LBB3 -> LBB4) { + LBB4 + if (!(LBB4 -> LBB5)) { + break; + } + while(1) { + LBB5 + if (LBB5 -> LBB6) { + break L5; + } } } -} +} while(0); LBB6 diff --git a/src/relooper/test4.txt b/src/relooper/test4.txt index 2ab3265a..f0bfb972 100644 --- a/src/relooper/test4.txt +++ b/src/relooper/test4.txt @@ -1,16 +1,17 @@ //19 -L1: -if ( 1 ) { - //20 - if (!( 1 )) { +do { + if ( 1 ) { + //20 + if (!( 1 )) { + label = 4; + break; + } + //21 + break; + } else { label = 4; - break L1; } - //21 - break L1; -} else { - label = 4; -} +} while(0); if (label == 4) { //22 } diff --git a/src/relooper/test6.txt b/src/relooper/test6.txt index 0ec7e666..c5effd08 100644 --- a/src/relooper/test6.txt +++ b/src/relooper/test6.txt @@ -1,11 +1,12 @@ //0 -L1: -if (check(0)) { - //1 - if (!(check(1))) { - break L1; +do { + if (check(0)) { + //1 + if (!(check(1))) { + break; + } + //2 } - //2 -} +} while(0); //3 diff --git a/src/relooper/test_debug.txt b/src/relooper/test_debug.txt index 02377fb7..1c7d0508 100644 --- a/src/relooper/test_debug.txt +++ b/src/relooper/test_debug.txt @@ -83,13 +83,14 @@ int main() { // === Optimizing shapes === // Fusing Multiple to Simple ep -L1: -if (ep -> LBB1) { - LBB1 - if (!(LBB1 -> LBB2)) { - break L1; +do { + if (ep -> LBB1) { + LBB1 + if (!(LBB1 -> LBB2)) { + break; + } + LBB2 } - LBB2 -} +} while(0); LBB3 diff --git a/src/relooper/test_fuzz1.txt b/src/relooper/test_fuzz1.txt index 09edb594..5122257e 100644 --- a/src/relooper/test_fuzz1.txt +++ b/src/relooper/test_fuzz1.txt @@ -3,12 +3,13 @@ print('entry'); var label; var state; var decisions = [4, 1, 7, 2, 6, 6, 8]; var index = 0; function check() { if (index == decisions.length) throw 'HALT'; return decisions[index++] } print(5); state = check(); print(6); state = check(); -L3: -if (state == 7) { - print(7); state = check(); - label = 3; - break L3; -} +do { + if (state == 7) { + print(7); state = check(); + label = 3; + break; + } +} while(0); L5: while(1) { if (label == 3) { label = 0; diff --git a/src/relooper/test_fuzz5.txt b/src/relooper/test_fuzz5.txt index 7c795d53..9548205c 100644 --- a/src/relooper/test_fuzz5.txt +++ b/src/relooper/test_fuzz5.txt @@ -3,21 +3,22 @@ print('entry'); var label; var state; var decisions = [133, 98, 134, 143, 162, 187, 130, 87, 91, 49, 102, 47, 9, 132, 179, 176, 157, 25, 64, 161, 57, 107, 16, 167, 185, 45, 191, 180, 23, 131]; var index = 0; function check() { if (index == decisions.length) throw 'HALT'; return decisions[index++] } L1: while(1) { print(7); state = check(); - L3: - if (state % 3 == 1) { - label = 3; - } else if (state % 3 == 0) { - print(8); state = check(); - if (state % 2 == 0) { - label = 5; - break L3; + do { + if (state % 3 == 1) { + label = 3; + } else if (state % 3 == 0) { + print(8); state = check(); + if (state % 2 == 0) { + label = 5; + break; + } else { + label = 7; + break; + } } else { - label = 7; - break L3; + break L1; } - } else { - break L1; - } + } while(0); while(1) { if (label == 3) { label = 0; diff --git a/src/relooper/test_inf.txt b/src/relooper/test_inf.txt index 3e292433..379d2083 100644 --- a/src/relooper/test_inf.txt +++ b/src/relooper/test_inf.txt @@ -5,34 +5,35 @@ if (uint(i4) >= uint(i5)) { code 1 } code 3 -L5: -if (!(i2 == 0)) { - code 4 - while(1) { - code 5 - if (uint(i6) >= uint(i7)) { - code 7 - } else { - code 6 - } - code 8 - if (uint(i6) >= uint(i7)) { - code 10 - } else { - code 9 - } - code 11 - if (uint(i5) >= uint(i6)) { - code 13 - } else { - code 12 - } - code 14 - if (!(i2 != 0)) { - break L5; +L5: do { + if (!(i2 == 0)) { + code 4 + while(1) { + code 5 + if (uint(i6) >= uint(i7)) { + code 7 + } else { + code 6 + } + code 8 + if (uint(i6) >= uint(i7)) { + code 10 + } else { + code 9 + } + code 11 + if (uint(i5) >= uint(i6)) { + code 13 + } else { + code 12 + } + code 14 + if (!(i2 != 0)) { + break L5; + } } } -} +} while(0); code 15 if (uint(i4) >= uint(i5)) { code 17 @@ -40,178 +41,179 @@ if (uint(i4) >= uint(i5)) { code 16 } code 18 -L26: -if (!(i2 == 0)) { - code 19 - while(1) { - code 20 - if (uint(i5) >= uint(i6)) { - code 22 - } else { - code 21 - } - code 23 - if (uint(i5) >= uint(i6)) { - code 25 - } else { - code 24 - } - code 26 - if (uint(i5) >= uint(i6)) { - code 28 - } else { - code 27 - } - code 29 - if (uint(i5) >= uint(i6)) { - code 31 - } else { - code 30 - } - code 32 - if (uint(i5) >= uint(i6)) { - code 34 - } else { - code 33 - } - code 35 - if (uint(i5) >= uint(i6)) { - code 37 - } else { - code 36 - } - code 38 - if (uint(i5) >= uint(i6)) { - code 40 - } else { - code 39 - } - code 41 - if (uint(i5) >= uint(i6)) { - code 43 - } else { - code 42 - } - code 44 - if (uint(i5) >= uint(i6)) { - code 46 - } else { - code 45 - } - code 47 - if (uint(i5) >= uint(i6)) { - code 49 - } else { - code 48 - } - code 50 - if (uint(i5) >= uint(i6)) { - code 52 - } else { - code 51 - } - code 53 - if (uint(i5) >= uint(i6)) { - code 55 - } else { - code 54 - } - code 56 - if (uint(i5) >= uint(i6)) { - code 58 - } else { - code 57 - } - code 59 - if (uint(i5) >= uint(i6)) { - code 61 - } else { - code 60 - } - code 62 - if (uint(i5) >= uint(i6)) { - code 64 - } else { - code 63 - } - code 65 - if (uint(i5) >= uint(i6)) { - code 67 - } else { - code 66 - } - code 68 - if (uint(i5) >= uint(i6)) { - code 70 - } else { - code 69 - } - code 71 - if (uint(i5) >= uint(i6)) { - code 73 - } else { - code 72 - } - code 74 - if (uint(i5) >= uint(i6)) { - code 76 - } else { - code 75 - } - code 77 - if (uint(i5) >= uint(i6)) { - code 79 - } else { - code 78 - } - code 80 - if (uint(i5) >= uint(i6)) { - code 82 - } else { - code 81 - } - code 83 - if (uint(i5) >= uint(i6)) { - code 85 - } else { - code 84 - } - code 86 - if (uint(i5) >= uint(i6)) { - code 88 - } else { - code 87 - } - code 89 - if (uint(i5) >= uint(i6)) { - code 91 - } else { - code 90 - } - code 92 - if (uint(i5) >= uint(i6)) { - code 94 - } else { - code 93 - } - code 95 - if (uint(i5) >= uint(i6)) { - code 97 - } else { - code 96 - } - code 98 - if (uint(i5) >= uint(i6)) { - code 100 - } else { - code 99 - } - code 101 - if (!(i2 != 0)) { - break L26; +L26: do { + if (!(i2 == 0)) { + code 19 + while(1) { + code 20 + if (uint(i5) >= uint(i6)) { + code 22 + } else { + code 21 + } + code 23 + if (uint(i5) >= uint(i6)) { + code 25 + } else { + code 24 + } + code 26 + if (uint(i5) >= uint(i6)) { + code 28 + } else { + code 27 + } + code 29 + if (uint(i5) >= uint(i6)) { + code 31 + } else { + code 30 + } + code 32 + if (uint(i5) >= uint(i6)) { + code 34 + } else { + code 33 + } + code 35 + if (uint(i5) >= uint(i6)) { + code 37 + } else { + code 36 + } + code 38 + if (uint(i5) >= uint(i6)) { + code 40 + } else { + code 39 + } + code 41 + if (uint(i5) >= uint(i6)) { + code 43 + } else { + code 42 + } + code 44 + if (uint(i5) >= uint(i6)) { + code 46 + } else { + code 45 + } + code 47 + if (uint(i5) >= uint(i6)) { + code 49 + } else { + code 48 + } + code 50 + if (uint(i5) >= uint(i6)) { + code 52 + } else { + code 51 + } + code 53 + if (uint(i5) >= uint(i6)) { + code 55 + } else { + code 54 + } + code 56 + if (uint(i5) >= uint(i6)) { + code 58 + } else { + code 57 + } + code 59 + if (uint(i5) >= uint(i6)) { + code 61 + } else { + code 60 + } + code 62 + if (uint(i5) >= uint(i6)) { + code 64 + } else { + code 63 + } + code 65 + if (uint(i5) >= uint(i6)) { + code 67 + } else { + code 66 + } + code 68 + if (uint(i5) >= uint(i6)) { + code 70 + } else { + code 69 + } + code 71 + if (uint(i5) >= uint(i6)) { + code 73 + } else { + code 72 + } + code 74 + if (uint(i5) >= uint(i6)) { + code 76 + } else { + code 75 + } + code 77 + if (uint(i5) >= uint(i6)) { + code 79 + } else { + code 78 + } + code 80 + if (uint(i5) >= uint(i6)) { + code 82 + } else { + code 81 + } + code 83 + if (uint(i5) >= uint(i6)) { + code 85 + } else { + code 84 + } + code 86 + if (uint(i5) >= uint(i6)) { + code 88 + } else { + code 87 + } + code 89 + if (uint(i5) >= uint(i6)) { + code 91 + } else { + code 90 + } + code 92 + if (uint(i5) >= uint(i6)) { + code 94 + } else { + code 93 + } + code 95 + if (uint(i5) >= uint(i6)) { + code 97 + } else { + code 96 + } + code 98 + if (uint(i5) >= uint(i6)) { + code 100 + } else { + code 99 + } + code 101 + if (!(i2 != 0)) { + break L26; + } } } -} +} while(0); code 102 if (uint(i4) >= uint(i5)) { code 104 @@ -219,136 +221,137 @@ if (uint(i4) >= uint(i5)) { code 103 } code 105 -L143: -if (!(i2 == 0)) { - code 106 - while(1) { - code 107 - if (uint(i5) >= uint(i6)) { - code 109 - } else { - code 108 - } - code 110 - if (uint(i5) >= uint(i6)) { - code 112 - } else { - code 111 - } - code 113 - if (uint(i5) >= uint(i6)) { - code 115 - } else { - code 114 - } - code 116 - if (uint(i5) >= uint(i6)) { - code 118 - } else { - code 117 - } - code 119 - if (uint(i5) >= uint(i6)) { - code 121 - } else { - code 120 - } - code 122 - if (uint(i5) >= uint(i6)) { - code 124 - } else { - code 123 - } - code 125 - if (uint(i5) >= uint(i6)) { - code 127 - } else { - code 126 - } - code 128 - if (uint(i5) >= uint(i6)) { - code 130 - } else { - code 129 - } - code 131 - if (uint(i5) >= uint(i6)) { - code 133 - } else { - code 132 - } - code 134 - if (uint(i5) >= uint(i6)) { - code 136 - } else { - code 135 - } - code 137 - if (uint(i5) >= uint(i6)) { - code 139 - } else { - code 138 - } - code 140 - if (uint(i5) >= uint(i6)) { - code 142 - } else { - code 141 - } - code 143 - if (uint(i5) >= uint(i6)) { - code 145 - } else { - code 144 - } - code 146 - if (uint(i5) >= uint(i6)) { - code 148 - } else { - code 147 - } - code 149 - if (uint(i5) >= uint(i6)) { - code 151 - } else { - code 150 - } - code 152 - if (uint(i5) >= uint(i6)) { - code 154 - } else { - code 153 - } - code 155 - if (uint(i5) >= uint(i6)) { - code 157 - } else { - code 156 - } - code 158 - if (uint(i5) >= uint(i6)) { - code 160 - } else { - code 159 - } - code 161 - if (uint(i5) >= uint(i6)) { - code 163 - } else { - code 162 - } - code 164 - if (uint(i5) >= uint(i6)) { - code 166 - } else { - code 165 - } - code 167 - if (!(i2 != 0)) { - break L143; +L143: do { + if (!(i2 == 0)) { + code 106 + while(1) { + code 107 + if (uint(i5) >= uint(i6)) { + code 109 + } else { + code 108 + } + code 110 + if (uint(i5) >= uint(i6)) { + code 112 + } else { + code 111 + } + code 113 + if (uint(i5) >= uint(i6)) { + code 115 + } else { + code 114 + } + code 116 + if (uint(i5) >= uint(i6)) { + code 118 + } else { + code 117 + } + code 119 + if (uint(i5) >= uint(i6)) { + code 121 + } else { + code 120 + } + code 122 + if (uint(i5) >= uint(i6)) { + code 124 + } else { + code 123 + } + code 125 + if (uint(i5) >= uint(i6)) { + code 127 + } else { + code 126 + } + code 128 + if (uint(i5) >= uint(i6)) { + code 130 + } else { + code 129 + } + code 131 + if (uint(i5) >= uint(i6)) { + code 133 + } else { + code 132 + } + code 134 + if (uint(i5) >= uint(i6)) { + code 136 + } else { + code 135 + } + code 137 + if (uint(i5) >= uint(i6)) { + code 139 + } else { + code 138 + } + code 140 + if (uint(i5) >= uint(i6)) { + code 142 + } else { + code 141 + } + code 143 + if (uint(i5) >= uint(i6)) { + code 145 + } else { + code 144 + } + code 146 + if (uint(i5) >= uint(i6)) { + code 148 + } else { + code 147 + } + code 149 + if (uint(i5) >= uint(i6)) { + code 151 + } else { + code 150 + } + code 152 + if (uint(i5) >= uint(i6)) { + code 154 + } else { + code 153 + } + code 155 + if (uint(i5) >= uint(i6)) { + code 157 + } else { + code 156 + } + code 158 + if (uint(i5) >= uint(i6)) { + code 160 + } else { + code 159 + } + code 161 + if (uint(i5) >= uint(i6)) { + code 163 + } else { + code 162 + } + code 164 + if (uint(i5) >= uint(i6)) { + code 166 + } else { + code 165 + } + code 167 + if (!(i2 != 0)) { + break L143; + } } } -} +} while(0); code 168 if (uint(i4) >= uint(i5)) { code 170 diff --git a/src/runtime.js b/src/runtime.js index e5b86065..dc604a8d 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -25,7 +25,7 @@ var RuntimeGenerator = { sep = sep || ';'; if (USE_TYPED_ARRAYS === 2) 'STACKTOP = (STACKTOP + STACKTOP|0 % ' + ({{{ QUANTUM_SIZE }}} - (isNumber(size) ? Math.min(size, {{{ QUANTUM_SIZE }}}) : {{{ QUANTUM_SIZE }}})) + ')' + sep; // The stack is always QUANTUM SIZE aligned, so we may not need to force alignment here - var ret = RuntimeGenerator.alloc(size, 'STACK', INIT_STACK, sep, USE_TYPED_ARRAYS != 2 || (isNumber(size) && parseInt(size) % {{{ QUANTUM_SIZE }}} == 0)); + 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)'; } @@ -45,7 +45,7 @@ var RuntimeGenerator = { if (ASSERTIONS) { ret += '; assert(STACKTOP < STACK_MAX)'; } - if (INIT_STACK) { + if (false) { ret += '; _memset(' + asmCoercion('__stackBase__', 'i32') + ', 0, ' + initial + ')'; } return ret; @@ -242,6 +242,10 @@ var Runtime = { } else if (Runtime.isStructType(field)) { size = Types.types[field].flatSize; alignSize = Types.types[field].alignSize; + } else if (field[0] == 'b') { + // bN, large number field, like a [N x i8] + size = field.substr(1)|0; + alignSize = 1; } else { throw 'Unclear type in struct: ' + field + ', in ' + type.name_ + ' :: ' + dump(Types.types[type.name_]); } @@ -353,7 +357,7 @@ var Runtime = { }, addFunction: function(func, sig) { - assert(sig); + //assert(sig); // TODO: support asm var table = FUNCTION_TABLE; // TODO: support asm var ret = table.length; table.push(func); @@ -361,6 +365,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]) { @@ -505,26 +514,19 @@ function getRuntime() { // example, -1 in int32 would be a very large number as unsigned. function unSign(value, bits, ignore, sig) { if (value >= 0) { -#if CHECK_SIGNS - if (!ignore) CorrectionsMonitor.note('UnSign', 1, sig); -#endif return value; } #if CHECK_SIGNS - if (!ignore) CorrectionsMonitor.note('UnSign', 0, sig); + if (!ignore) throw 'UnSign'; #endif return bits <= 32 ? 2*Math.abs(1 << (bits-1)) + value // Need some trickery, since if bits == 32, we are right at the limit of the bits JS uses in bitshifts : Math.pow(2, bits) + value; - // TODO: clean up previous line } // Converts a value we have as unsigned, into a signed value. For // example, 200 in a uint8 would be a negative number. function reSign(value, bits, ignore, sig) { if (value <= 0) { -#if CHECK_SIGNS - if (!ignore) CorrectionsMonitor.note('ReSign', 1, sig); -#endif return value; } var half = bits <= 32 ? Math.abs(1 << (bits-1)) // abs is needed if bits == 32 @@ -536,10 +538,7 @@ function reSign(value, bits, ignore, sig) { // but, in general there is no perfect solution here. With 64-bit ints, we get rounding and errors // TODO: In i64 mode 1, resign the two parts separately and safely #if CHECK_SIGNS - if (!ignore) { - CorrectionsMonitor.note('ReSign', 0, sig); - noted = true; - } + if (!ignore) throw 'ReSign'; #endif value = -2*half + value; // Cannot bitshift half, as it may be at the limit of the bits JS uses in bitshifts } @@ -548,18 +547,9 @@ function reSign(value, bits, ignore, sig) { // without CHECK_SIGNS, we would just do the |0 shortcut, so check that that // would indeed give the exact same result. if (bits === 32 && (value|0) !== value && typeof value !== 'boolean') { - if (!ignore) { - CorrectionsMonitor.note('ReSign', 0, sig); - noted = true; - } + if (!ignore) throw 'ReSign'; } - if (!noted) CorrectionsMonitor.note('ReSign', 1, sig); #endif return value; } -// Just a stub. We don't care about noting compile-time corrections. But they are called. -var CorrectionsMonitor = { - note: function(){} -}; - diff --git a/src/settings.js b/src/settings.js index 0234d0ca..1bfcf92a 100644 --- a/src/settings.js +++ b/src/settings.js @@ -37,7 +37,6 @@ var VERBOSE = 0; // When set to 1, will generate more verbose output during comp var INVOKE_RUN = 1; // Whether we will call run(). Disable if you embed the generated // code in your own, and will call run() yourself at the right time -var INIT_STACK = 0; // Whether to initialize memory on the stack to 0. var INIT_HEAP = 0; // Whether to initialize memory anywhere other than the stack to 0. var TOTAL_STACK = 5*1024*1024; // The total stack size. There is no way to enlarge the stack, so this // value must be large enough for the program's requirements. If @@ -59,6 +58,8 @@ var ALLOW_MEMORY_GROWTH = 0; // If false, we abort with an error if we try to al // Code embetterments var MICRO_OPTS = 1; // Various micro-optimizations, like nativizing variables var RELOOP = 0; // Recreate js native loops from llvm data +var RELOOPER = 'relooper.js'; // Loads the relooper from this path relative to compiler.js + 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), @@ -92,13 +93,9 @@ var PRECISE_I64_MATH = 1; // If enabled, i64 addition etc. is emulated - which i // that we can't know at compile time that 64-bit math is needed. For example, if you // print 64-bit values with printf, but never add them, we can't know at compile time // and you need to set this to 2. -var PRECISE_I32_MUL = 0; // If enabled, i64 math is done in i32 multiplication. This is necessary if the values - // exceed the JS double-integer limit of ~52 bits. This option can normally be disabled - // because generally i32 multiplication works ok without it, and enabling it has a big - // impact on performance. - // Note that you can hand-optimize your code to avoid the need for this: If you do - // multiplications that actually need 64-bit precision inside 64-bit values, things - // will work properly. (Unless the LLVM optimizer turns them into 32-bit values?) +var PRECISE_I32_MUL = 1; // If enabled, i32 multiplication is done with full precision, which means it is + // correct even if the value exceeds the JS double-integer limit of ~52 bits (otherwise, + // rounding will occur above that range). var CLOSURE_ANNOTATIONS = 0; // If set, the generated code will be annotated for the closure // compiler. This potentially lets closure optimize the code better. @@ -111,10 +108,11 @@ var SKIP_STACK_IN_SMALL = 1; // When enabled, does not push/pop the stack at all // In particular, be careful with the autodebugger! (We do turn // this off automatically in that case, though.) var INLINE_LIBRARY_FUNCS = 1; // Will inline library functions that have __inline defined -var INLINING_LIMIT = 50; // A limit on inlining. If 0, we will inline normally in LLVM and +var INLINING_LIMIT = 0; // A limit on inlining. If 0, we will inline normally in LLVM and // closure. If greater than 0, we will *not* inline in LLVM, and // we will prevent inlining of functions of this size or larger - // in closure. + // in closure. 50 is a reasonable setting if you do not want + // inlining var CATCH_EXIT_CODE = 0; // If set, causes exit() to throw an exception object which is caught // in a try..catch block and results in the exit status being // returned from run(). If zero (the default), the program is just @@ -132,8 +130,23 @@ var SAFE_HEAP = 0; // Check each write to the heap, for example, this will give // that 3 is the option you usually want here. var SAFE_HEAP_LOG = 0; // Log out all SAFE_HEAP operations +var CHECK_HEAP_ALIGN = 0; // Check heap accesses for alignment, but don't do as + // near extensive (or slow) checks as SAFE_HEAP. + +var SAFE_DYNCALLS = 0; // Show stack traces on missing function pointer/virtual method calls + var ASM_HEAP_LOG = 0; // Simple heap logging, like SAFE_HEAP_LOG but cheaper, and in asm.js +var CORRUPTION_CHECK = 0; // When enabled, will emit a buffer area at the beginning and + // end of each allocation on the heap, filled with canary + // values that can be checked later. Corruption is checked for + // at the end of each at each free() (see jsifier to add more, and you + // can add more manual checks by calling CorruptionChecker.checkAll). + // 0 means not enabled, higher values mean the size of the + // buffer areas as a multiple of the allocated area (so + // 1 means 100%, or buffer areas equal to allocated area, + // both before and after). This must be an integer. + var LABEL_DEBUG = 0; // 1: Print out functions as we enter them // 2: Also print out each label as we enter it var LABEL_FUNCTION_FILTERS = []; // Filters for function label debug. @@ -148,11 +161,15 @@ var LIBRARY_DEBUG = 0; // Print out when we enter a library call (library*.js). // Runtime.debug at runtime for logging to cease, and can set it when you // want it back. A simple way to set it in C++ is // emscripten_run_script("Runtime.debug = ...;"); -var GL_DEBUG = 0; // Print out all calls into WebGL. As with LIBRARY_DEBUG, you can set a runtime - // option, in this case GL.debug. var SOCKET_DEBUG = 0; // Log out socket/network data transfer. -var PROFILE_MAIN_LOOP = 0; // Profile the function called in set_main_loop +var GL_DEBUG = 0; // Print out all calls into WebGL. As with LIBRARY_DEBUG, you can set a runtime + // option, in this case GL.debug. +var GL_TESTING = 0; // When enabled, sets preserveDrawingBuffer in the context, to allow tests to work (but adds overhead) +var GL_MAX_TEMP_BUFFER_SIZE = 2097152; // How large GL emulation temp buffers are +var GL_UNSAFE_OPTS = 1; // Enables some potentially-unsafe optimizations in GL emulation code +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 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 @@ -203,21 +220,10 @@ var FS_LOG = 0; // Log all FS operations. This is especially helpful when you'r // a new project and want to see a list of file system operations happening // so that you can create a virtual file system with all of the required files. -var PGO = 0; // Profile-guided optimization. - // When run with the CHECK_* options, will not fail on errors. Instead, will - // keep a record of which checks succeeded and which failed. On shutdown, will - // print out that information. This is useful for knowing which lines need - // checking enabled and which do not, that is, this is a way to automate the - // generation of line data for CORRECT_*_LINES options. - // All CORRECT_* options default to 1 with PGO builds. - // See https://github.com/kripken/emscripten/wiki/Optimizing-Code for more info - var NAMED_GLOBALS = 0; // If 1, we use global variables for globals. Otherwise // they are referred to by a base plus an offset (called an indexed global), // saving global variables but adding runtime overhead. -var PROFILE = 0; // Enables runtime profiling. See test_profiling for a usage example. - var EXPORT_ALL = 0; // If true, we export all the symbols var EXPORTED_FUNCTIONS = ['_main']; // Functions that are explicitly exported. These functions are kept alive // through LLVM dead code elimination, and also made accessible outside of @@ -325,12 +331,13 @@ var BENCHMARK = 0; // If 1, will just time how long main() takes to execute, and var ASM_JS = 0; // If 1, generate code in asm.js format. XXX This is highly experimental, // and will not work on most codebases yet. It is NOT recommended that you // try this yet. -var USE_MATH_IMUL = 0; // If 1, use Math.imul when useful 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. +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/src/shell.html b/src/shell.html index 3a0171de..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> @@ -32,6 +39,7 @@ var element = document.getElementById('output'); element.value = ''; // clear browser cache return function(text) { + text = Array.prototype.slice.call(arguments).join(' '); // These replacements are necessary if you render to raw HTML //text = text.replace(/&/g, "&"); //text = text.replace(/</g, "<"); @@ -42,6 +50,7 @@ }; })(), printErr: function(text) { + text = Array.prototype.slice.call(arguments).join(' '); if (0) { // XXX disabled for safety typeof dump == 'function') { dump(text + '\n'); // fast, straight to the real console } else { |