From 6a6bb771d6d7786064331afab47943d65b0d4fd6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 13:09:29 -0700 Subject: [PATCH 01/61] add fasta_float test --- tests/test_core.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index dd5b1e39ad380..74569c7f5c0f9 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -8305,6 +8305,12 @@ def test_fasta(self): src = open(path_from_root('tests', 'fasta.cpp'), 'r').read() self.do_run(src, j, [str(i)], lambda x, err: x.replace('\n', '*'), no_build=i>1) + if Settings.ASM_JS: + # test float support in asm + i, j = results[-1] + src = open(path_from_root('tests', 'fasta.cpp'), 'r').read().replace('double', 'float') + self.do_run(src, j, [str(i)], lambda x, err: x.replace('\n', '*')) + def test_whets(self): if not Settings.ASM_JS: return self.skip('mainly a test for asm validation here') self.do_run(open(path_from_root('tests', 'whets.cpp')).read(), 'Single Precision C Whetstone Benchmark') From 3ff3a402d58a8c33151d6ce6767d34cb5bc9cc29 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 13:15:44 -0700 Subject: [PATCH 02/61] move to FROUND option for Math.fround --- emscripten.py | 2 +- src/parseTools.js | 4 ++-- src/preamble.js | 10 ++++------ src/settings.js | 2 +- tests/test_core.py | 1 + 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/emscripten.py b/emscripten.py index dbea6eb22baaa..8829a50fb4c9e 100755 --- a/emscripten.py +++ b/emscripten.py @@ -451,7 +451,7 @@ def fix_item(item): math_envs = ['Math.min'] # TODO: move min to maths asm_setup += '\n'.join(['var %s = %s;' % (f.replace('.', '_'), f) for f in math_envs]) - if settings['TO_FLOAT32']: maths += ['Math.toFloat32'] + if settings['FROUND']: maths += ['Math.fround'] basic_funcs = ['abort', 'assert', 'asmPrintInt', 'asmPrintFloat'] + [m.replace('.', '_') for m in math_envs] if settings['RESERVED_FUNCTION_POINTERS'] > 0: basic_funcs.append('jsCall') diff --git a/src/parseTools.js b/src/parseTools.js index dae386f179a57..2272c85167ef8 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -2139,8 +2139,8 @@ function makeIsNaN(value) { } function makeFloat(value, type) { - if (TO_FLOAT32 && type == 'float') { - return 'Math_toFloat32(' + value + ')'; + if (FROUND && type == 'float') { + return 'Math_fround(' + value + ')'; } return value; } diff --git a/src/preamble.js b/src/preamble.js index ee273f6a36eda..f00e59e0e723b 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -1074,11 +1074,9 @@ Math['imul'] = function(a, b) { #endif Math.imul = Math['imul']; -#if TO_FLOAT32 -if (!Math['toFloat32']) Math['toFloat32'] = function(x) { - return x; -}; -Math.toFloat32 = Math['toFloat32']; +#if FROUND +if (!Math['fround']) Math['fround'] = function(x) { return x }; +Math.fround = Math['fround']; #endif var Math_abs = Math.abs; @@ -1096,7 +1094,7 @@ var Math_ceil = Math.ceil; var Math_floor = Math.floor; var Math_pow = Math.pow; var Math_imul = Math.imul; -var Math_toFloat32 = Math.toFloat32; +var Math_fround = Math.fround; var Math_min = Math.min; // A counter of dependencies for calling run(). If we need to diff --git a/src/settings.js b/src/settings.js index d2b47dc88c38f..3ea513cb1409e 100644 --- a/src/settings.js +++ b/src/settings.js @@ -115,7 +115,7 @@ var PRECISE_I64_MATH = 1; // If enabled, i64 addition etc. is emulated - which i 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 TO_FLOAT32 = 0; // Use Math.toFloat32 +var FROUND = 0; // Use Math.fround (polyfilling when necessary) var CLOSURE_ANNOTATIONS = 0; // If set, the generated code will be annotated for the closure // compiler. This potentially lets closure optimize the code better. diff --git a/tests/test_core.py b/tests/test_core.py index 74569c7f5c0f9..88d1e34cae8c5 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -8307,6 +8307,7 @@ def test_fasta(self): if Settings.ASM_JS: # test float support in asm + Settings.FROUND = 1 i, j = results[-1] src = open(path_from_root('tests', 'fasta.cpp'), 'r').read().replace('double', 'float') self.do_run(src, j, [str(i)], lambda x, err: x.replace('\n', '*')) From 96b514ca183befe9bd800ce16f33a61c5f971627 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 13:29:00 -0700 Subject: [PATCH 03/61] use fround in asm initializers and coercions --- src/parseTools.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/parseTools.js b/src/parseTools.js index 2272c85167ef8..14683bf617248 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1194,11 +1194,9 @@ function asmEnsureFloat(value, type) { // ensures that a float type has either 5 function asmInitializer(type, impl) { if (type in Runtime.FLOAT_TYPES) { - if (RUNNING_JS_OPTS) { - return '+0'; - } else { - return '.0'; - } + var ret = RUNNING_JS_OPTS ? '+0' : '.0'; + if (FROUND && type === 'float') ret = 'Math_fround(' + ret + ')'; + return ret; } else { return '0'; } @@ -1219,7 +1217,11 @@ function asmCoercion(value, type, signedness) { value = '(' + value + ')|0'; } } - return '(+(' + value + '))'; + if (FROUND && type === 'float') { + return 'Math_fround(' + value + ')'; + } else { + return '(+(' + value + '))'; + } } } else { return '((' + value + ')|0)'; From 845a37cffb7d134291d6e9245b03ba3cb01b4133 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 13:48:05 -0700 Subject: [PATCH 04/61] fround on numerical constants, and fix some related parse issues --- src/parseTools.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/parseTools.js b/src/parseTools.js index 14683bf617248..134c6a83002f8 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -479,10 +479,13 @@ function parseParamTokens(params) { segment[0].text += segment[1].text; segment.splice(1, 1); // TODO: merge tokens nicely } + var num = isNumber(segment[1].text); + var ident = parseNumerical(segment[1].text, segment[0].text); + if (!num) ident = toNiceIdent(ident); ret.push({ intertype: 'value', type: segment[0].text, - ident: toNiceIdent(parseNumerical(segment[1].text, segment[0].text)) + ident: ident }); Types.needAnalysis[removeAllPointing(ret[ret.length-1].type)] = 0; } @@ -569,7 +572,7 @@ function finalizeParam(param) { if (param.type == 'i64' && USE_TYPED_ARRAYS == 2) { return parseI64Constant(param.ident); } - var ret = toNiceIdent(param.ident); + var ret = param.ident; if (ret in Variables.globals) { ret = makeGlobalUse(ret); } @@ -1177,6 +1180,7 @@ function asmEnsureFloat(value, type) { // ensures that a float type has either 5 if (!ASM_JS) return value; // coerce if missing a '.', or if smaller than 1, so could be 1e-5 which has no . if (type in Runtime.FLOAT_TYPES && isNumber(value) && (value.toString().indexOf('.') < 0 || Math.abs(value) < 1)) { + if (FROUND && type === 'float') return 'Math_fround(' + value + ')'; if (RUNNING_JS_OPTS) { return '(+' + value + ')'; // JS optimizer will run, we must do +x, and it will be corrected later } else { From cba6d823fc2db9400621325ea0a87c1a3f3a913f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 13:52:27 -0700 Subject: [PATCH 05/61] fix int to float conversions to differentiate float from double --- src/parseTools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parseTools.js b/src/parseTools.js index 134c6a83002f8..77925876df351 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -2445,7 +2445,7 @@ function processMathop(item) { case 'fdiv': return makeFloat(getFastValue(idents[0], '/', idents[1], item.type), item.type); case 'fmul': return makeFloat(getFastValue(idents[0], '*', idents[1], item.type), item.type); case 'frem': return makeFloat(getFastValue(idents[0], '%', idents[1], item.type), item.type); - case 'uitofp': case 'sitofp': return asmCoercion(idents[0], 'double', op[0]); + case 'uitofp': case 'sitofp': return asmCoercion(idents[0], item.type, op[0]); case 'fptoui': case 'fptosi': return makeRounding(idents[0], bitsLeft, op === 'fptosi', true); // TODO: We sometimes generate false instead of 0, etc., in the *cmps. It seemed slightly faster before, but worth rechecking From 2a8b87bcc174a17390f0d3799de8a6d2d86c2820 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 14:13:20 -0700 Subject: [PATCH 06/61] simplify fround initializer --- src/parseTools.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/parseTools.js b/src/parseTools.js index 77925876df351..a6f2e35e6997c 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1198,9 +1198,8 @@ function asmEnsureFloat(value, type) { // ensures that a float type has either 5 function asmInitializer(type, impl) { if (type in Runtime.FLOAT_TYPES) { - var ret = RUNNING_JS_OPTS ? '+0' : '.0'; - if (FROUND && type === 'float') ret = 'Math_fround(' + ret + ')'; - return ret; + if (FROUND && type === 'float') return 'Math_fround(0)'; + return RUNNING_JS_OPTS ? '+0' : '.0'; } else { return '0'; } From 80df6c605bfba43a46eb71cdcaa6ceba50a05871 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 14:36:22 -0700 Subject: [PATCH 07/61] respect fround in fptrunc/ext --- src/parseTools.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/parseTools.js b/src/parseTools.js index a6f2e35e6997c..a61b5035b4acd 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -2491,8 +2491,15 @@ function processMathop(item) { } // otherwise, fall through } - case 'fpext': case 'sext': return idents[0]; - case 'fptrunc': return idents[0]; + case 'sext': return idents[0]; + case 'fpext': { + if (FROUND) return '+(' + idents[0] + ')'; + return idents[0]; + } + case 'fptrunc': { + if (FROUND) return 'Math_fround(' + idents[0] + ')'; + return idents[0]; + } case 'select': return idents[0] + '?' + asmEnsureFloat(idents[1], item.type) + ':' + asmEnsureFloat(idents[2], item.type); case 'ptrtoint': case 'inttoptr': { var ret = ''; From 7cc79ee531f2c44e5cbf3d071b205924578acffb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 14:44:15 -0700 Subject: [PATCH 08/61] add fpext and fptrunc to list of llvm conversions, so we differentiate float/double in them --- src/modules.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules.js b/src/modules.js index 854575e076716..814817a3bb8e3 100644 --- a/src/modules.js +++ b/src/modules.js @@ -18,7 +18,7 @@ var LLVM = { PHI_REACHERS: set('branch', 'switch', 'invoke', 'indirectbr'), EXTENDS: set('sext', 'zext'), COMPS: set('icmp', 'fcmp'), - CONVERSIONS: set('inttoptr', 'ptrtoint', 'uitofp', 'sitofp', 'fptosi', 'fptoui'), + CONVERSIONS: set('inttoptr', 'ptrtoint', 'uitofp', 'sitofp', 'fptosi', 'fptoui', 'fpext', 'fptrunc'), INTRINSICS_32: set('_llvm_memcpy_p0i8_p0i8_i64', '_llvm_memmove_p0i8_p0i8_i64', '_llvm_memset_p0i8_i64'), // intrinsics that need args converted to i32 in USE_TYPED_ARRAYS == 2 }; LLVM.GLOBAL_MODIFIERS = set(keys(LLVM.LINKAGES).concat(['constant', 'global', 'hidden'])); From c777166191bfef6d5ed3da760722ea9028983e6f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 16:02:35 -0700 Subject: [PATCH 09/61] properly apply fround to all float constants, even if they are fractions and look like doubles --- src/parseTools.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/parseTools.js b/src/parseTools.js index a61b5035b4acd..8322fb74ce3d4 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1178,9 +1178,10 @@ function makeVarDef(js) { function asmEnsureFloat(value, type) { // ensures that a float type has either 5.5 (clearly a float) or +5 (float due to asm coercion) if (!ASM_JS) return value; + if (!isNumber(value)) return value; + if (FROUND && type === 'float') return 'Math_fround(' + value + ')'; // coerce if missing a '.', or if smaller than 1, so could be 1e-5 which has no . - if (type in Runtime.FLOAT_TYPES && isNumber(value) && (value.toString().indexOf('.') < 0 || Math.abs(value) < 1)) { - if (FROUND && type === 'float') return 'Math_fround(' + value + ')'; + if (type in Runtime.FLOAT_TYPES && (value.toString().indexOf('.') < 0 || Math.abs(value) < 1)) { if (RUNNING_JS_OPTS) { return '(+' + value + ')'; // JS optimizer will run, we must do +x, and it will be corrected later } else { From c92280c3557c9faf182ffac60de856f1de6770ac Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 16:19:46 -0700 Subject: [PATCH 10/61] emit tempFloat global --- emscripten.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emscripten.py b/emscripten.py index 8829a50fb4c9e..76056491923c0 100755 --- a/emscripten.py +++ b/emscripten.py @@ -585,7 +585,7 @@ def math_fix(g): var undef = 0; var tempInt = 0, tempBigInt = 0, tempBigIntP = 0, tempBigIntS = 0, tempBigIntR = 0.0, tempBigIntI = 0, tempBigIntD = 0, tempValue = 0, tempDouble = 0.0; ''' + ''.join([''' - var tempRet%d = 0;''' % i for i in range(10)]) + '\n' + asm_global_funcs + ''' + var tempRet%d = 0;''' % i for i in range(10)]) + '\n' + asm_global_funcs] + [' var tempFloat = %s;\n' % ('Math_fround(0)' if settings.get('FROUND') else '0.0')] + [''' // EMSCRIPTEN_START_FUNCS function stackAlloc(size) { size = size|0; From ca3f2f004d51268fbf3a9455d2f0c5569b15599d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 16:22:41 -0700 Subject: [PATCH 11/61] use float temporary in makeIsNaN when relevant --- src/parseTools.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/parseTools.js b/src/parseTools.js index 8322fb74ce3d4..f8d7e6ac8e6d6 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -2139,8 +2139,8 @@ function makeRounding(value, bits, signed, floatConversion) { } } -function makeIsNaN(value) { - if (ASM_JS) return makeInlineCalculation('((VALUE) != (VALUE))', value, 'tempDouble'); +function makeIsNaN(value, type) { + if (ASM_JS) return makeInlineCalculation('((VALUE) != (VALUE))', value, type === 'float' ? 'tempFloat' : 'tempDouble'); return 'isNaN(' + value + ')'; } @@ -2477,8 +2477,8 @@ function processMathop(item) { case 'ult': case 'olt': return idents[0] + '<' + idents[1]; case 'une': case 'one': return idents[0] + '!=' + idents[1]; case 'ueq': case 'oeq': return idents[0] + '==' + idents[1]; - case 'ord': return '!' + makeIsNaN(idents[0]) + '&!' + makeIsNaN(idents[1]); - case 'uno': return makeIsNaN(idents[0]) + '|' + makeIsNaN(idents[1]); + case 'ord': return '!' + makeIsNaN(idents[0], paramTypes[0]) + '&!' + makeIsNaN(idents[1], paramTypes[0]); + case 'uno': return makeIsNaN(idents[0], paramTypes[0]) + '|' + makeIsNaN(idents[1], paramTypes[0]); case 'true': return '1'; default: throw 'Unknown fcmp variant: ' + variant; } From 5041ed44a66938588e674b9f7c03b9281b99c4e1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 16:26:58 -0700 Subject: [PATCH 12/61] simplify auto-return adding code, and make it generic so it can handle floats as well --- src/jsifier.js | 15 ++------------- src/parseTools.js | 2 +- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/jsifier.js b/src/jsifier.js index b36e11ed0d3cb..7a2fdde0a6433 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -756,14 +756,7 @@ function JSify(data, functionsOnly, givenFunctions) { if (func.setjmpTable && !ASM_JS) { ret += ' } catch(e) { if (!e.longjmp || !(e.id in mySetjmpIds)) throw(e); setjmpTable[setjmpLabels[e.id]](e.value) }'; } - if (ASM_JS && func.returnType !== 'void') { - // Add a return - if (func.returnType in Runtime.FLOAT_TYPES) { - ret += ' return +0;\n'; - } else { - ret += ' return 0;\n'; - } - } + if (ASM_JS && func.returnType !== 'void') ret += ' return ' + asmInitializer(func.returnType) + ';\n'; // Add a return } else { ret += (SHOW_LABELS ? indent + '/* ' + block.entries[0] + ' */' : '') + '\n' + getLabelLines(block.labels[0]); } @@ -833,11 +826,7 @@ function JSify(data, functionsOnly, givenFunctions) { var lastReturn = func.JS.lastIndexOf('return '); if ((lastCurly < 0 && lastReturn < 0) || // no control flow, no return (lastCurly >= 0 && lastReturn < lastCurly)) { // control flow, no return past last join - if (func.returnType in Runtime.FLOAT_TYPES) { - func.JS += ' return +0;\n'; - } else { - func.JS += ' return 0;\n'; - } + func.JS += ' return ' + asmInitializer(func.returnType) + ';\n'; } } func.JS += '}\n'; diff --git a/src/parseTools.js b/src/parseTools.js index f8d7e6ac8e6d6..3489964f21915 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1197,7 +1197,7 @@ function asmEnsureFloat(value, type) { // ensures that a float type has either 5 } } -function asmInitializer(type, impl) { +function asmInitializer(type) { if (type in Runtime.FLOAT_TYPES) { if (FROUND && type === 'float') return 'Math_fround(0)'; return RUNNING_JS_OPTS ? '+0' : '.0'; From 296cd5f54cb598fad7c9e58776bac906da1add02 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 16:51:18 -0700 Subject: [PATCH 13/61] support fround in function tables and asm module support functions --- emscripten.py | 22 +++++++--------------- src/jsifier.js | 2 +- src/modules.js | 41 +++++++++++++++++++++++++++-------------- tools/shared.py | 22 ++++++++++++++++++++++ 4 files changed, 57 insertions(+), 30 deletions(-) diff --git a/emscripten.py b/emscripten.py index 76056491923c0..24e38f4499d3e 100755 --- a/emscripten.py +++ b/emscripten.py @@ -11,6 +11,7 @@ import os, sys, json, optparse, subprocess, re, time, multiprocessing, string, logging +from tools import shared from tools import jsrun, cache as cache_module, tempfiles from tools.response_file import read_response_file @@ -25,7 +26,6 @@ def get_configuration(): if hasattr(get_configuration, 'configuration'): return get_configuration.configuration - from tools import shared configuration = shared.Configuration(environ=os.environ) get_configuration.configuration = configuration return configuration @@ -425,8 +425,8 @@ def make_table(sig, raw): Counter.i += 1 bad = 'b' + str(i) params = ','.join(['p%d' % p for p in range(len(sig)-1)]) - coercions = ';'.join(['p%d = %sp%d%s' % (p, '+' if sig[p+1] != 'i' else '', p, '' if sig[p+1] != 'i' else '|0') for p in range(len(sig)-1)]) + ';' - ret = '' if sig[0] == 'v' else ('return %s0' % ('+' if sig[0] != 'i' else '')) + coercions = ';'.join(['p%d = %s' % (p, shared.JS.make_coercion('p%d' % p, sig[p+1], settings)) for p in range(len(sig)-1)]) + ';' + ret = '' if sig[0] == 'v' else ('return %s' % shared.JS.make_initializer(sig[0], settings)) start = raw.index('[') end = raw.rindex(']') body = raw[start+1:end].split(',') @@ -476,18 +476,14 @@ def fix_item(item): asm_runtime_funcs = ['stackAlloc', 'stackSave', 'stackRestore', 'setThrew'] + ['setTempRet%d' % i for i in range(10)] # function tables - def asm_coerce(value, sig): - if sig == 'v': return value - return ('+' if sig != 'i' else '') + value + ('|0' if sig == 'i' else '') - function_tables = ['dynCall_' + table for table in last_forwarded_json['Functions']['tables']] function_tables_impls = [] for sig in last_forwarded_json['Functions']['tables'].iterkeys(): args = ','.join(['a' + str(i) for i in range(1, len(sig))]) - arg_coercions = ' '.join(['a' + str(i) + '=' + asm_coerce('a' + str(i), sig[i]) + ';' for i in range(1, len(sig))]) - coerced_args = ','.join([asm_coerce('a' + str(i), sig[i]) for i in range(1, len(sig))]) - ret = ('return ' if sig[0] != 'v' else '') + asm_coerce('FUNCTION_TABLE_%s[index&{{{ FTM_%s }}}](%s)' % (sig, sig, coerced_args), sig[0]) + arg_coercions = ' '.join(['a' + str(i) + '=' + shared.JS.make_coercion('a' + str(i), sig[i], settings) + ';' for i in range(1, len(sig))]) + coerced_args = ','.join([shared.JS.make_coercion('a' + str(i), sig[i], settings) for i in range(1, len(sig))]) + ret = ('return ' if sig[0] != 'v' else '') + shared.JS.make_coercion('FUNCTION_TABLE_%s[index&{{{ FTM_%s }}}](%s)' % (sig, sig, coerced_args), sig[0], settings) function_tables_impls.append(''' function dynCall_%s(index%s%s) { index = index|0; @@ -497,7 +493,7 @@ def asm_coerce(value, sig): ''' % (sig, ',' if len(sig) > 1 else '', args, arg_coercions, ret)) for i in range(settings['RESERVED_FUNCTION_POINTERS']): - jsret = ('return ' if sig[0] != 'v' else '') + asm_coerce('jsCall(%d%s%s)' % (i, ',' if coerced_args else '', coerced_args), sig[0]) + jsret = ('return ' if sig[0] != 'v' else '') + shared.JS.make_coercion('jsCall(%d%s%s)' % (i, ',' if coerced_args else '', coerced_args), sig[0], settings) function_tables_impls.append(''' function jsCall_%s_%s(%s) { %s @@ -505,7 +501,6 @@ def asm_coerce(value, sig): } ''' % (sig, i, args, arg_coercions, jsret)) - from tools import shared shared.Settings.copy(settings) asm_setup += '\n' + shared.JS.make_invoke(sig) + '\n' basic_funcs.append('invoke_%s' % sig) @@ -727,14 +722,12 @@ def main(args, compiler_engine, cache, jcache, relooper, temp_files, DEBUG, DEBU relooper = cache.get_path('relooper.js') settings.setdefault('RELOOPER', relooper) if not os.path.exists(relooper): - from tools import shared shared.Building.ensure_relooper(relooper) settings.setdefault('STRUCT_INFO', cache.get_path('struct_info.compiled.json')) struct_info = settings.get('STRUCT_INFO') if not os.path.exists(struct_info): - from tools import shared shared.Building.ensure_struct_info(struct_info) emscript(args.infile, settings, args.outfile, libraries, compiler_engine=compiler_engine, @@ -833,7 +826,6 @@ def _main(environ): temp_files = tempfiles.TempFiles(temp_dir) if keywords.compiler is None: - from tools import shared keywords.compiler = shared.COMPILER_ENGINE if keywords.verbose is None: diff --git a/src/jsifier.js b/src/jsifier.js index 7a2fdde0a6433..79ad9c0374884 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -1579,7 +1579,7 @@ function JSify(data, functionsOnly, givenFunctions) { returnType = getReturnType(type); if (callIdent in Functions.implementedFunctions) { // LLVM sometimes bitcasts for no reason. We must call using the exact same type as the actual function is generated as - var trueType = Functions.getSignatureReturnType(Functions.implementedFunctions[callIdent]); + var trueType = Functions.getSignatureType(Functions.implementedFunctions[callIdent][0]); if (trueType !== returnType && !isIdenticallyImplemented(trueType, returnType)) { if (VERBOSE) warnOnce('Fixing function call based on return type from signature, on ' + [callIdent, returnType, trueType]); returnType = trueType; diff --git a/src/modules.js b/src/modules.js index 814817a3bb8e3..2e6edeba4b882 100644 --- a/src/modules.js +++ b/src/modules.js @@ -253,13 +253,32 @@ var Functions = { aliases: {}, // in shared modules (MAIN_MODULE or SHARED_MODULE), a list of aliases for functions that have them + getSignatureLetter: function(type) { + switch(type) { + case 'float': return 'f'; + case 'double': return 'd'; + case 'void': return 'v'; + default: return 'i'; + } + }, + + getSignatureType: function(letter) { + switch(letter) { + case 'v': return 'void'; + case 'i': return 'i32'; + case 'f': return 'float'; + case 'd': return 'double'; + default: throw 'what is this sig? ' + sig; + } + }, + getSignature: function(returnType, argTypes, hasVarArgs) { - var sig = returnType == 'void' ? 'v' : (isIntImplemented(returnType) ? 'i' : 'f'); + var sig = Functions.getSignatureLetter(returnType); for (var i = 0; i < argTypes.length; i++) { var type = argTypes[i]; if (!type) break; // varargs if (type in Runtime.FLOAT_TYPES) { - sig += 'f'; + sig += Functions.getSignatureLetter(type); } else { var chunks = getNumIntChunks(type); for (var j = 0; j < chunks; j++) sig += 'i'; @@ -269,15 +288,6 @@ var Functions = { return sig; }, - getSignatureReturnType: function(sig) { - switch(sig[0]) { - case 'v': return 'void'; - case 'i': return 'i32'; - case 'f': return 'double'; - default: throw 'what is this sig? ' + sig; - } - }, - // Mark a function as needing indexing. Python will coordinate them all getIndex: function(ident, sig) { var ret; @@ -353,14 +363,17 @@ var Functions = { if (t[0] == 'i') { retPre = 'return '; retPost = '|0'; - } else { + } else if (t[0] === 'd') { retPre = 'return +'; + } else { + retPre = 'return Math_fround('; + retPost = ')'; } } for (var j = 1; j < t.length; j++) { args += (j > 1 ? ',' : '') + 'a' + j; - arg_coercions += 'a' + j + '=' + asmCoercion('a' + j, t[j] != 'i' ? 'float' : 'i32') + ';'; - call += (j > 1 ? ',' : '') + asmCoercion('a' + j, t[j] != 'i' ? 'float' : 'i32'); + arg_coercions += 'a' + j + '=' + asmCoercion('a' + j, Functions.getSignatureType(t[j])) + ';'; + call += (j > 1 ? ',' : '') + asmCoercion('a' + j, Functions.getSignatureType(t[j])); } call += ')'; if (short == '_setjmp') printErr('WARNING: setjmp used via a function pointer. If this is for libc setjmp (not something of your own with the same name), it will break things'); diff --git a/tools/shared.py b/tools/shared.py index 108a48a48087e..a923f88a3f079 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -1510,6 +1510,28 @@ class JS: def to_nice_ident(ident): # limited version of the JS function toNiceIdent return ident.replace('%', '$').replace('@', '_') + @staticmethod + def make_initializer(sig, settings=None): + settings = settings or Settings + if sig == 'i': + return '0' + elif sig == 'f' and settings.get('FROUND'): + return 'Math_fround(0)' + else: + return '+0' + + @staticmethod + def make_coercion(value, sig, settings=None): + settings = settings or Settings + if sig == 'i': + return value + '|0' + elif sig == 'f' and settings.get('FROUND'): + return 'Math_fround(' + value + ')' + elif sig == 'd' or sig == 'f': + return '+' + value + else: + return value + @staticmethod def make_extcall(sig, named=True): args = ','.join(['a' + str(i) for i in range(1, len(sig))]) From 24f2b64781720ada9821bfd6eb8deddd72242dcb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 17:45:50 -0700 Subject: [PATCH 14/61] do not mangle hex float constants in function calls --- src/parseTools.js | 2 +- src/utility.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/parseTools.js b/src/parseTools.js index 3489964f21915..455cbe8b78dbe 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -479,7 +479,7 @@ function parseParamTokens(params) { segment[0].text += segment[1].text; segment.splice(1, 1); // TODO: merge tokens nicely } - var num = isNumber(segment[1].text); + var num = isNumber(segment[1].text) || (segment[1].text[0] === '0'); // handle 0x... as well var ident = parseNumerical(segment[1].text, segment[0].text); if (!num) ident = toNiceIdent(ident); ret.push({ diff --git a/src/utility.js b/src/utility.js index ac821a89924f0..62adb807cefaf 100644 --- a/src/utility.js +++ b/src/utility.js @@ -222,6 +222,7 @@ function mergeInto(obj, other) { } function isNumber(x) { + // XXX this does not handle 0xabc123 etc return x == parseFloat(x) || (typeof x == 'string' && x.match(/^-?\d+$/)); } From c9cd4323d8091da25c1a2e84aa5e27033a97b590 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 18:00:19 -0700 Subject: [PATCH 15/61] fix splitI64 use of float instead of double --- src/parseTools.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parseTools.js b/src/parseTools.js index 455cbe8b78dbe..90970e1ffcac8 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -801,8 +801,8 @@ function splitI64(value, floatConversion) { var high = makeInlineCalculation( asmCoercion('Math_abs(VALUE)', 'double') + ' >= ' + asmEnsureFloat('1', 'double') + ' ? ' + '(VALUE > ' + asmEnsureFloat('0', 'double') + ' ? ' + - asmCoercion('Math_min(' + asmCoercion('Math_floor((VALUE)/' + asmEnsureFloat(4294967296, 'float') + ')', 'double') + ', ' + asmEnsureFloat(4294967295, 'float') + ')', 'i32') + '>>>0' + - ' : ' + asmFloatToInt(asmCoercion('Math_ceil((VALUE - +((' + asmFloatToInt('VALUE') + ')>>>0))/' + asmEnsureFloat(4294967296, 'float') + ')', 'double')) + '>>>0' + + asmCoercion('Math_min(' + asmCoercion('Math_floor((VALUE)/' + asmEnsureFloat(4294967296, 'double') + ')', 'double') + ', ' + asmEnsureFloat(4294967295, 'double') + ')', 'i32') + '>>>0' + + ' : ' + asmFloatToInt(asmCoercion('Math_ceil((VALUE - +((' + asmFloatToInt('VALUE') + ')>>>0))/' + asmEnsureFloat(4294967296, 'double') + ')', 'double')) + '>>>0' + ')' + ' : 0', value, From 866039dc1f1c880399d3819a1194350d78c12a33 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 18:09:47 -0700 Subject: [PATCH 16/61] do not send floats to ffis --- src/modules.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules.js b/src/modules.js index 2e6edeba4b882..dd622bee351c8 100644 --- a/src/modules.js +++ b/src/modules.js @@ -372,8 +372,9 @@ var Functions = { } for (var j = 1; j < t.length; j++) { args += (j > 1 ? ',' : '') + 'a' + j; - arg_coercions += 'a' + j + '=' + asmCoercion('a' + j, Functions.getSignatureType(t[j])) + ';'; - call += (j > 1 ? ',' : '') + asmCoercion('a' + j, Functions.getSignatureType(t[j])); + var type = Functions.getSignatureType(t[j]); + arg_coercions += 'a' + j + '=' + asmCoercion('a' + j, type) + ';'; + call += (j > 1 ? ',' : '') + asmCoercion('a' + j, type === 'float' ? 'double' : type); // ffi arguments must be doubles if they are floats } call += ')'; if (short == '_setjmp') printErr('WARNING: setjmp used via a function pointer. If this is for libc setjmp (not something of your own with the same name), it will break things'); From e0053d0115e7028a990aa3d189209c3cc22919c6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 18:20:58 -0700 Subject: [PATCH 17/61] detect NaN as a number --- src/utility.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utility.js b/src/utility.js index 62adb807cefaf..bec0eb8f4c579 100644 --- a/src/utility.js +++ b/src/utility.js @@ -222,8 +222,8 @@ function mergeInto(obj, other) { } function isNumber(x) { - // XXX this does not handle 0xabc123 etc - return x == parseFloat(x) || (typeof x == 'string' && x.match(/^-?\d+$/)); + // XXX this does not handle 0xabc123 etc. We should likely also do x == parseInt(x) (which handles that), and remove hack |// handle 0x... as well| + return x == parseFloat(x) || (typeof x == 'string' && x.match(/^-?\d+$/)) || x === 'NaN'; } function isArray(x) { From 3351bf7019d1bba9d62426a4ec77d098775e3f08 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2013 18:28:24 -0700 Subject: [PATCH 18/61] send and receive only doubles through ffis, not floats --- src/jsifier.js | 12 +++++++++--- src/parseTools.js | 4 ++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/jsifier.js b/src/jsifier.js index 79ad9c0374884..329b06c63e878 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -1501,8 +1501,9 @@ function JSify(data, functionsOnly, givenFunctions) { args = args.map(function(arg, i) { return indexizeFunctions(arg, argsTypes[i]) }); if (ASM_JS) { - if (shortident in Functions.libraryFunctions || simpleIdent in Functions.libraryFunctions || byPointerForced || invoke || extCall || funcData.setjmpTable) { - args = args.map(function(arg, i) { return asmCoercion(arg, argsTypes[i]) }); + var ffiCall = shortident in Functions.libraryFunctions || simpleIdent in Functions.libraryFunctions || byPointerForced || invoke || extCall || funcData.setjmpTable; + if (ffiCall) { + args = args.map(function(arg, i) { return asmCoercion(arg, ensureValidFFIType(argsTypes[i])) }); } else { args = args.map(function(arg, i) { return asmEnsureFloat(arg, argsTypes[i]) }); } @@ -1615,7 +1616,12 @@ function JSify(data, functionsOnly, givenFunctions) { var ret = callIdent + '(' + args.join(',') + ')'; if (ASM_JS) { // TODO: do only when needed (library functions and Math.*?) XXX && simpleIdent in Functions.libraryFunctions) { - ret = asmCoercion(ret, returnType); + if (ffiCall) { + ret = asmCoercion(ret, ensureValidFFIType(returnType)); + if (FROUND && returnType === 'float') ret = asmCoercion(ret, 'float'); // receive double, then float it + } else { + ret = asmCoercion(ret, returnType); + } if (simpleIdent == 'abort' && funcData.returnType != 'void') { ret += '; return ' + asmCoercion('0', funcData.returnType); // special case: abort() can happen without return, breaking the return type of asm functions. ensure a return } diff --git a/src/parseTools.js b/src/parseTools.js index 90970e1ffcac8..803208268925f 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -2691,3 +2691,7 @@ function ensureVector(ident, base) { return ident == 0 ? base + '32x4.zero()' : ident; } +function ensureValidFFIType(type) { + return type === 'float' ? 'double' : type; // ffi does not tolerate float XXX +} + From 76014aec3125274b975196103259018f37785451 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Oct 2013 14:20:25 -0700 Subject: [PATCH 19/61] use doubles in makeBigInt --- src/runtime.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime.js b/src/runtime.js index 5d5cb43b47783..ed624fbf76142 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -82,8 +82,8 @@ var RuntimeGenerator = { // Rounding is inevitable if the number is large. This is a particular problem for small negative numbers // (-1 will be rounded!), so handle negatives separately and carefully makeBigInt: function(low, high, unsigned) { - var unsignedRet = '(' + asmCoercion(makeSignOp(low, 'i32', 'un', 1, 1), 'float') + '+(' + asmCoercion(makeSignOp(high, 'i32', 'un', 1, 1), 'float') + '*' + asmEnsureFloat(4294967296, 'float') + '))'; - var signedRet = '(' + asmCoercion(makeSignOp(low, 'i32', 'un', 1, 1), 'float') + '+(' + asmCoercion(makeSignOp(high, 'i32', 're', 1, 1), 'float') + '*' + asmEnsureFloat(4294967296, 'float') + '))'; + var unsignedRet = '(' + asmCoercion(makeSignOp(low, 'i32', 'un', 1, 1), 'double') + '+(' + asmCoercion(makeSignOp(high, 'i32', 'un', 1, 1), 'double') + '*' + asmEnsureFloat(4294967296, 'double') + '))'; + var signedRet = '(' + asmCoercion(makeSignOp(low, 'i32', 'un', 1, 1), 'double') + '+(' + asmCoercion(makeSignOp(high, 'i32', 're', 1, 1), 'double') + '*' + asmEnsureFloat(4294967296, 'double') + '))'; if (typeof unsigned === 'string') return '(' + unsigned + ' ? ' + unsignedRet + ' : ' + signedRet + ')'; return unsigned ? unsignedRet : signedRet; } From 2f2f3ca9fbbe5906bb68aa56edf7eca192ac9dc1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Oct 2013 14:36:15 -0700 Subject: [PATCH 20/61] use asm ffi coercing for floats in load abort calls --- src/jsifier.js | 5 ++--- src/parseTools.js | 7 +++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/jsifier.js b/src/jsifier.js index 329b06c63e878..669cec7368ac9 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -1324,7 +1324,7 @@ function JSify(data, functionsOnly, givenFunctions) { if (isNumber(item.ident)) { // 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 asmCoercion('abort(' + item.ident + ')', item.type); + return asmFFICoercion('abort(' + item.ident + ')', item.type); } else { item.assignTo = null; return 'throw "fault on read from ' + item.ident + '";'; @@ -1617,8 +1617,7 @@ function JSify(data, functionsOnly, givenFunctions) { var ret = callIdent + '(' + args.join(',') + ')'; if (ASM_JS) { // TODO: do only when needed (library functions and Math.*?) XXX && simpleIdent in Functions.libraryFunctions) { if (ffiCall) { - ret = asmCoercion(ret, ensureValidFFIType(returnType)); - if (FROUND && returnType === 'float') ret = asmCoercion(ret, 'float'); // receive double, then float it + ret = asmFFICoercion(ret, returnType); } else { ret = asmCoercion(ret, returnType); } diff --git a/src/parseTools.js b/src/parseTools.js index 803208268925f..9089a9e6864ac 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -2695,3 +2695,10 @@ function ensureValidFFIType(type) { return type === 'float' ? 'double' : type; // ffi does not tolerate float XXX } +// FFI return values must arrive as doubles, and we can force them to floats afterwards +function asmFFICoercion(value, type) { + value = asmCoercion(value, ensureValidFFIType(type)); + if (FROUND && type === 'float') value = asmCoercion(value, 'float'); + return value; +} + From 7b44c1589112d5f3df207b1fee5d19c49dab9cc9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Oct 2013 14:38:33 -0700 Subject: [PATCH 21/61] cast to double before float in function pointer wrappers --- src/modules.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules.js b/src/modules.js index dd622bee351c8..b8301d71d1275 100644 --- a/src/modules.js +++ b/src/modules.js @@ -366,8 +366,8 @@ var Functions = { } else if (t[0] === 'd') { retPre = 'return +'; } else { - retPre = 'return Math_fround('; - retPost = ')'; + retPre = 'return Math_fround(+('; + retPost = '))'; } } for (var j = 1; j < t.length; j++) { From 7c909b0db1a699f9a4f71189a56e51c065557a41 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Oct 2013 14:48:40 -0700 Subject: [PATCH 22/61] use double for emscripten_get_now, to not lose precision from performance.now etc. --- system/include/emscripten/emscripten.h | 2 +- tests/emscripten_get_now.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/system/include/emscripten/emscripten.h b/system/include/emscripten/emscripten.h index d30620eca762f..dd1e01a43edb3 100644 --- a/system/include/emscripten/emscripten.h +++ b/system/include/emscripten/emscripten.h @@ -203,7 +203,7 @@ void emscripten_get_canvas_size(int *width, int *height, int *isFullscreen); * absolute time, and is only meaningful in comparison to * other calls to this function. The unit is ms. */ -float emscripten_get_now(); +double emscripten_get_now(); /* * Simple random number generation in [0, 1), maps to Math.random(). diff --git a/tests/emscripten_get_now.cpp b/tests/emscripten_get_now.cpp index 17aa7d32db814..5ededb230509e 100644 --- a/tests/emscripten_get_now.cpp +++ b/tests/emscripten_get_now.cpp @@ -14,10 +14,10 @@ int main() { // b) Values returned by emscripten_get_now() are strictly nondecreasing. // c) emscripten_get_now() is able to return sub-millisecond precision timer values. bool detected_good_timer_precision = false; - float smallest_delta = 0.f; + double smallest_delta = 0.f; for(int x = 0; x < 1000; ++x) { // Have several attempts to find a good small delta, i.e. give time to JS engine to warm up the code and so on. - float t = emscripten_get_now(); - float t2 = emscripten_get_now(); + double t = emscripten_get_now(); + double t2 = emscripten_get_now(); for(int i = 0; i < 100 && t == t2; ++i) { t2 = emscripten_get_now(); } From c03de3f080fc942ea29b403c37c538697d047b6e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Oct 2013 16:46:03 -0700 Subject: [PATCH 23/61] simplify wrapper asm coercions --- src/modules.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/modules.js b/src/modules.js index b8301d71d1275..13cca9775c5c3 100644 --- a/src/modules.js +++ b/src/modules.js @@ -360,15 +360,9 @@ var Functions = { if (!wrapped[curr]) { var args = '', arg_coercions = '', call = short + '(', retPre = '', retPost = ''; if (t[0] != 'v') { - if (t[0] == 'i') { - retPre = 'return '; - retPost = '|0'; - } else if (t[0] === 'd') { - retPre = 'return +'; - } else { - retPre = 'return Math_fround(+('; - retPost = '))'; - } + var temp = asmFFICoercion('X', Functions.getSignatureType(t[0])).split('X'); + retPre = 'return ' + temp[0]; + retPost = temp[1]; } for (var j = 1; j < t.length; j++) { args += (j > 1 ? ',' : '') + 'a' + j; From 048595a2580c75e8cbfb8fc93ca0ea7c9c21dc45 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Oct 2013 18:11:04 -0700 Subject: [PATCH 24/61] ensure a dot in large float32 constants --- src/parseTools.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/parseTools.js b/src/parseTools.js index 9089a9e6864ac..1f4847172af90 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1176,21 +1176,29 @@ function makeVarDef(js) { return js; } +function ensureDot(value) { + value = value.toString(); + if (value.indexOf('.') >= 0 || /[IN]/.test(value)) return value; // if already dotted, or Infinity or NaN, nothing to do here + var e = value.indexOf('e'); + if (e < 0) return value + '.0'; + return value.substr(0, e) + '.0' + value.substr(e); +} + function asmEnsureFloat(value, type) { // ensures that a float type has either 5.5 (clearly a float) or +5 (float due to asm coercion) if (!ASM_JS) return value; if (!isNumber(value)) return value; - if (FROUND && type === 'float') return 'Math_fround(' + value + ')'; + if (FROUND && type === 'float') { + // normally ok to just emit Math_fround(0), but if the constant is large we may need a .0 (if it can't fit in an int) + if (value == 0) return 'Math_fround(0)'; + value = ensureDot(value); + return 'Math_fround(' + value + ')'; + } // coerce if missing a '.', or if smaller than 1, so could be 1e-5 which has no . if (type in Runtime.FLOAT_TYPES && (value.toString().indexOf('.') < 0 || Math.abs(value) < 1)) { if (RUNNING_JS_OPTS) { return '(+' + value + ')'; // JS optimizer will run, we must do +x, and it will be corrected later } else { - // ensure a . - value = value.toString(); - if (value.indexOf('.') >= 0 || /[IN]/.test(value)) return value; // if already dotted, or Infinity or NaN, nothing to do here - var e = value.indexOf('e'); - if (e < 0) return value + '.0'; - return value.substr(0, e) + '.0' + value.substr(e); + return ensureDot(value); } } else { return value; From eee7d81f248104d2bde5d64ed1f3f5111f78b93d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Oct 2013 18:13:27 -0700 Subject: [PATCH 25/61] fix uitofp of i64 to float32 --- src/parseTools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parseTools.js b/src/parseTools.js index 1f4847172af90..79a8837383783 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -2271,7 +2271,7 @@ function processMathop(item) { case 'lshr': { throw 'shifts should have been legalized!'; } - case 'uitofp': case 'sitofp': return RuntimeGenerator.makeBigInt(low1, high1, op[0] == 'u'); + case 'uitofp': case 'sitofp': return makeFloat(RuntimeGenerator.makeBigInt(low1, high1, op[0] == 'u'), item.type); case 'fptoui': case 'fptosi': return finish(splitI64(idents[0], true)); case 'icmp': { switch (variant) { From 2e50e7ca8ae5f6e18894c74fe4d33c90e404c6bc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Oct 2013 18:17:02 -0700 Subject: [PATCH 26/61] improve fasta float32 testing --- tests/test_core.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 88d1e34cae8c5..7a53098c32350 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -8305,9 +8305,12 @@ def test_fasta(self): src = open(path_from_root('tests', 'fasta.cpp'), 'r').read() self.do_run(src, j, [str(i)], lambda x, err: x.replace('\n', '*'), no_build=i>1) - if Settings.ASM_JS: + assert 'asm1' in test_modes + if self.run_name == 'asm1': # test float support in asm - Settings.FROUND = 1 + print 'was fround', Settings.FROUND + Settings.FROUND = 1 - Settings.FROUND + print 'now fround variant', Settings.FROUND i, j = results[-1] src = open(path_from_root('tests', 'fasta.cpp'), 'r').read().replace('double', 'float') self.do_run(src, j, [str(i)], lambda x, err: x.replace('\n', '*')) From 0154b1effe2cde0ad81a1adcbe83b7c9d018dbbd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Oct 2013 18:38:52 -0700 Subject: [PATCH 27/61] add test for precise float32 support, allow 3 modes of precise float32 support, and rename option to PRECISE_F32 to be consistent with other precision options --- emscripten.py | 4 ++-- src/parseTools.js | 14 +++++++------- src/preamble.js | 7 ++++++- src/settings.js | 8 +++++++- tests/test_core.py | 29 +++++++++++++++++++++++++++++ tools/shared.py | 4 ++-- 6 files changed, 53 insertions(+), 13 deletions(-) diff --git a/emscripten.py b/emscripten.py index 24e38f4499d3e..3383ca662b3ff 100755 --- a/emscripten.py +++ b/emscripten.py @@ -451,7 +451,7 @@ def fix_item(item): math_envs = ['Math.min'] # TODO: move min to maths asm_setup += '\n'.join(['var %s = %s;' % (f.replace('.', '_'), f) for f in math_envs]) - if settings['FROUND']: maths += ['Math.fround'] + if settings['PRECISE_F32']: maths += ['Math.fround'] basic_funcs = ['abort', 'assert', 'asmPrintInt', 'asmPrintFloat'] + [m.replace('.', '_') for m in math_envs] if settings['RESERVED_FUNCTION_POINTERS'] > 0: basic_funcs.append('jsCall') @@ -580,7 +580,7 @@ def math_fix(g): var undef = 0; var tempInt = 0, tempBigInt = 0, tempBigIntP = 0, tempBigIntS = 0, tempBigIntR = 0.0, tempBigIntI = 0, tempBigIntD = 0, tempValue = 0, tempDouble = 0.0; ''' + ''.join([''' - var tempRet%d = 0;''' % i for i in range(10)]) + '\n' + asm_global_funcs] + [' var tempFloat = %s;\n' % ('Math_fround(0)' if settings.get('FROUND') else '0.0')] + [''' + var tempRet%d = 0;''' % i for i in range(10)]) + '\n' + asm_global_funcs] + [' var tempFloat = %s;\n' % ('Math_fround(0)' if settings.get('PRECISE_F32') else '0.0')] + [''' // EMSCRIPTEN_START_FUNCS function stackAlloc(size) { size = size|0; diff --git a/src/parseTools.js b/src/parseTools.js index 79a8837383783..4dbabbb019fa0 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1187,7 +1187,7 @@ function ensureDot(value) { function asmEnsureFloat(value, type) { // ensures that a float type has either 5.5 (clearly a float) or +5 (float due to asm coercion) if (!ASM_JS) return value; if (!isNumber(value)) return value; - if (FROUND && type === 'float') { + if (PRECISE_F32 && type === 'float') { // normally ok to just emit Math_fround(0), but if the constant is large we may need a .0 (if it can't fit in an int) if (value == 0) return 'Math_fround(0)'; value = ensureDot(value); @@ -1207,7 +1207,7 @@ function asmEnsureFloat(value, type) { // ensures that a float type has either 5 function asmInitializer(type) { if (type in Runtime.FLOAT_TYPES) { - if (FROUND && type === 'float') return 'Math_fround(0)'; + if (PRECISE_F32 && type === 'float') return 'Math_fround(0)'; return RUNNING_JS_OPTS ? '+0' : '.0'; } else { return '0'; @@ -1229,7 +1229,7 @@ function asmCoercion(value, type, signedness) { value = '(' + value + ')|0'; } } - if (FROUND && type === 'float') { + if (PRECISE_F32 && type === 'float') { return 'Math_fround(' + value + ')'; } else { return '(+(' + value + '))'; @@ -2153,7 +2153,7 @@ function makeIsNaN(value, type) { } function makeFloat(value, type) { - if (FROUND && type == 'float') { + if (PRECISE_F32 && type == 'float') { return 'Math_fround(' + value + ')'; } return value; @@ -2502,11 +2502,11 @@ function processMathop(item) { } case 'sext': return idents[0]; case 'fpext': { - if (FROUND) return '+(' + idents[0] + ')'; + if (PRECISE_F32) return '+(' + idents[0] + ')'; return idents[0]; } case 'fptrunc': { - if (FROUND) return 'Math_fround(' + idents[0] + ')'; + if (PRECISE_F32) return 'Math_fround(' + idents[0] + ')'; return idents[0]; } case 'select': return idents[0] + '?' + asmEnsureFloat(idents[1], item.type) + ':' + asmEnsureFloat(idents[2], item.type); @@ -2706,7 +2706,7 @@ function ensureValidFFIType(type) { // FFI return values must arrive as doubles, and we can force them to floats afterwards function asmFFICoercion(value, type) { value = asmCoercion(value, ensureValidFFIType(type)); - if (FROUND && type === 'float') value = asmCoercion(value, 'float'); + if (PRECISE_F32 && type === 'float') value = asmCoercion(value, 'float'); return value; } diff --git a/src/preamble.js b/src/preamble.js index f00e59e0e723b..8ab6d6040f799 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -1074,8 +1074,13 @@ Math['imul'] = function(a, b) { #endif Math.imul = Math['imul']; -#if FROUND +#if PRECISE_F32 +#if PRECISE_F32 == 1 +var froundBuffer = new Float32Array(1); +if (!Math['fround']) Math['fround'] = function(x) { froundBuffer[0] = x; return froundBuffer[0] }; +#else // 2 if (!Math['fround']) Math['fround'] = function(x) { return x }; +#endif Math.fround = Math['fround']; #endif diff --git a/src/settings.js b/src/settings.js index 3ea513cb1409e..de9ab46c21f10 100644 --- a/src/settings.js +++ b/src/settings.js @@ -115,7 +115,13 @@ var PRECISE_I64_MATH = 1; // If enabled, i64 addition etc. is emulated - which i 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 FROUND = 0; // Use Math.fround (polyfilling when necessary) +var PRECISE_F32 = 0; // 0: Use JS numbers for floating-point values. These are 64-bit and do not model C++ + // floats exactly, which are 32-bit. + // 1: Model C++ floats precisely, using Math.fround, polyfilling when necessary. This + // can be slow if the polyfill is used on heavy float32 computation. + // 2: Model C++ floats precisely using Math.fround if available in the JS engine, otherwise + // use an empty polyfill. This will have less of a speed penalty than using the full + // polyfill in cases where engine support is not present. var CLOSURE_ANNOTATIONS = 0; // If set, the generated code will be annotated for the closure // compiler. This potentially lets closure optimize the code better. diff --git a/tests/test_core.py b/tests/test_core.py index 7a53098c32350..f2376b5333d44 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -882,6 +882,35 @@ def test_double_i64_conversion(self): ''' self.do_run(src, 'OK!\n'); + def test_float32_precise(self): + assert 'asm1' in test_modes + if self.run_name != 'asm1': return self.skip('todo') + + Settings.PRECISE_F32 = 1 + + src = r''' + #include + + int main(int argc, char **argv) { + float x = 1.23456789123456789; + float y = 5.20456089123406709; + while (argc > 10 || argc % 19 == 15) { + // confuse optimizer + x /= y; + y = 2*y - 1; + argc--; + } + x = x - y; + y = 3*y - x/2; + x = x*y; + y += 0.000000000123123123123; + x -= y/7.654; + printf("\n%.20f, %.20f\n", x, y); + return 0; + } + ''' + self.do_run(src, '\n-72.16590881347656250000, 17.59867858886718750000\n') + def test_negative_zero(self): src = r''' #include diff --git a/tools/shared.py b/tools/shared.py index a923f88a3f079..6ba6ef50c6fcd 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -1515,7 +1515,7 @@ def make_initializer(sig, settings=None): settings = settings or Settings if sig == 'i': return '0' - elif sig == 'f' and settings.get('FROUND'): + elif sig == 'f' and settings.get('PRECISE_F32'): return 'Math_fround(0)' else: return '+0' @@ -1525,7 +1525,7 @@ def make_coercion(value, sig, settings=None): settings = settings or Settings if sig == 'i': return value + '|0' - elif sig == 'f' and settings.get('FROUND'): + elif sig == 'f' and settings.get('PRECISE_F32'): return 'Math_fround(' + value + ')' elif sig == 'd' or sig == 'f': return '+' + value From 75af3c1ac8dba93b508b8b431bef9a35e6b054c3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Oct 2013 18:44:35 -0700 Subject: [PATCH 28/61] add asm1f test mode --- tests/runner.py | 2 +- tests/test_core.py | 16 ++-------------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/tests/runner.py b/tests/runner.py index 867f71139e9a2..483df36e3f410 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -36,7 +36,7 @@ def path_from_root(*pathelems): # Core test runner class, shared between normal tests and benchmarks checked_sanity = False -test_modes = ['default', 'o1', 'o2', 'asm1', 'asm2', 'asm2g', 'asm2x86', 's_0_0', 's_0_1'] +test_modes = ['default', 'o1', 'o2', 'asm1', 'asm1f', 'asm2', 'asm2g', 'asm2x86', 's_0_0', 's_0_1'] test_index = 0 class RunnerCore(unittest.TestCase): diff --git a/tests/test_core.py b/tests/test_core.py index f2376b5333d44..ca81102daf21c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -883,10 +883,7 @@ def test_double_i64_conversion(self): self.do_run(src, 'OK!\n'); def test_float32_precise(self): - assert 'asm1' in test_modes - if self.run_name != 'asm1': return self.skip('todo') - - Settings.PRECISE_F32 = 1 + if self.emcc_args == None or 'PRECISE_F32=1' not in self.emcc_args: return self.skip('needs precise float32') src = r''' #include @@ -8334,16 +8331,6 @@ def test_fasta(self): src = open(path_from_root('tests', 'fasta.cpp'), 'r').read() self.do_run(src, j, [str(i)], lambda x, err: x.replace('\n', '*'), no_build=i>1) - assert 'asm1' in test_modes - if self.run_name == 'asm1': - # test float support in asm - print 'was fround', Settings.FROUND - Settings.FROUND = 1 - Settings.FROUND - print 'now fround variant', Settings.FROUND - i, j = results[-1] - src = open(path_from_root('tests', 'fasta.cpp'), 'r').read().replace('double', 'float') - self.do_run(src, j, [str(i)], lambda x, err: x.replace('\n', '*')) - def test_whets(self): if not Settings.ASM_JS: return self.skip('mainly a test for asm validation here') self.do_run(open(path_from_root('tests', 'whets.cpp')).read(), 'Single Precision C Whetstone Benchmark') @@ -10611,6 +10598,7 @@ def setUp(self): # asm.js asm1 = make_run("asm1", compiler=CLANG, emcc_args=["-O1"]) +asm1f = make_run("asm1f", compiler=CLANG, emcc_args=["-O1", "-s", "PRECISE_F32=1"]) asm2 = make_run("asm2", compiler=CLANG, emcc_args=["-O2"]) asm2g = make_run("asm2g", compiler=CLANG, emcc_args=["-O2", "-g", "-s", "ASSERTIONS=1", "--memory-init-file", "1", "-s", "CHECK_HEAP_ALIGN=1"]) asm2x86 = make_run("asm2x86", compiler=CLANG, emcc_args=["-O2", "-g", "-s", "CHECK_HEAP_ALIGN=1"], env={"EMCC_LLVM_TARGET": "i386-pc-linux-gnu"}) From 140ea9e81feb09d8f2559995c73d49a39424ef5d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 1 Nov 2013 18:39:33 -0700 Subject: [PATCH 29/61] do not emit unnecessary coercions when calling a math builtin with a float --- src/jsifier.js | 3 ++- src/parseTools.js | 2 ++ tests/test_core.py | 8 ++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/jsifier.js b/src/jsifier.js index d5b87a773ea63..97317756d3027 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -1503,7 +1503,8 @@ function JSify(data, functionsOnly, givenFunctions) { args = args.map(function(arg, i) { return indexizeFunctions(arg, argsTypes[i]) }); if (ASM_JS) { - var ffiCall = shortident in Functions.libraryFunctions || simpleIdent in Functions.libraryFunctions || byPointerForced || invoke || extCall || funcData.setjmpTable; + var ffiCall = (shortident in Functions.libraryFunctions || simpleIdent in Functions.libraryFunctions || byPointerForced || invoke || extCall || funcData.setjmpTable) && + !(simpleIdent in JS_MATH_BUILTINS); if (ffiCall) { args = args.map(function(arg, i) { return asmCoercion(arg, ensureValidFFIType(argsTypes[i])) }); } else { diff --git a/src/parseTools.js b/src/parseTools.js index 6179e285f2fc8..34e064f0256ec 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -629,6 +629,8 @@ function cleanSegment(segment) { var MATHOPS = set(['add', 'sub', 'sdiv', 'udiv', 'mul', 'icmp', 'zext', 'urem', 'srem', 'fadd', 'fsub', 'fmul', 'fdiv', 'fcmp', 'frem', 'uitofp', 'sitofp', 'fpext', 'fptrunc', 'fptoui', 'fptosi', 'trunc', 'sext', 'select', 'shl', 'shr', 'ashl', 'ashr', 'lshr', 'lshl', 'xor', 'or', 'and', 'ptrtoint', 'inttoptr']); +var JS_MATH_BUILTINS = set(['Math_sin', 'Math_cos', 'Math_tan', 'Math_asin', 'Math_acos', 'Math_atan', 'Math_ceil', 'Math_floor', 'Math_exp', 'Math_log', 'Math_sqrt']); + var PARSABLE_LLVM_FUNCTIONS = set('getelementptr', 'bitcast'); mergeInto(PARSABLE_LLVM_FUNCTIONS, MATHOPS); diff --git a/tests/test_core.py b/tests/test_core.py index a19cde109a66d..f3a106a2eef8a 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1516,7 +1516,7 @@ def test_math(self): #include #include #include - int main() + int main(int argc, char **argv) { printf("*%.2f,%.2f,%d", M_PI, -M_PI, (1/0.0) > 1e300); // could end up as infinity, or just a very very big number printf(",%d", isfinite(NAN) != 0); @@ -1538,11 +1538,15 @@ def test_math(self): sincosf(0.0, &fsine, &fcosine); printf(",%1.1f", fsine); printf(",%1.1f", fcosine); + fsine = sinf(1.1 + argc - 1); + fcosine = cosf(1.1 + argc - 1); + printf(",%1.1f", fsine); + printf(",%1.1f", fcosine); printf("*\\n"); return 0; } ''' - self.do_run(src, '*3.14,-3.14,1,0,0,0,1,0,1,1,0,2,3,0.0,1.0,0.0,1.0*') + self.do_run(src, '*3.14,-3.14,1,0,0,0,1,0,1,1,0,2,3,0.0,1.0,0.0,1.0,0.9,0.5*') def test_erf(self): src = ''' From 24c8ebb2f0ee4717bc39f3236583f6a329cc6b64 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 8 Nov 2013 17:33:56 -0800 Subject: [PATCH 30/61] add asm2f test subsuite --- tests/test_core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_core.py b/tests/test_core.py index 53d16f9afde60..eec42c8a52113 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -10736,6 +10736,7 @@ def setUp(self): asm1 = make_run("asm1", compiler=CLANG, emcc_args=["-O1"]) asm1f = make_run("asm1f", compiler=CLANG, emcc_args=["-O1", "-s", "PRECISE_F32=1"]) asm2 = make_run("asm2", compiler=CLANG, emcc_args=["-O2"]) +asm2f = make_run("asm2f", compiler=CLANG, emcc_args=["-O2", "-s", "PRECISE_F32=1", "-g"]) # TODO: collapse into another test subsuite asm2g = make_run("asm2g", compiler=CLANG, emcc_args=["-O2", "-g", "-s", "ASSERTIONS=1", "--memory-init-file", "1", "-s", "CHECK_HEAP_ALIGN=1"]) asm2x86 = make_run("asm2x86", compiler=CLANG, emcc_args=["-O2", "-g", "-s", "CHECK_HEAP_ALIGN=1"], env={"EMCC_LLVM_TARGET": "i386-pc-linux-gnu"}) From d041b0fe9f08069b5786d505cfe958899031b521 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 8 Nov 2013 18:01:46 -0800 Subject: [PATCH 31/61] initial work for float32 support in js optimizer --- tools/js-optimizer.js | 54 +++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 36244298431f9..cba3186115ef6 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -1515,21 +1515,33 @@ function unVarify(vars, ret) { // transform var x=1, y=2 etc. into (x=1, y=2), i // annotations, plus explicit metadata) and denormalize (vice versa) var ASM_INT = 0; var ASM_DOUBLE = 1; +var ASM_FLOAT = 2; function detectAsmCoercion(node, asmInfo) { // for params, +x vs x|0, for vars, 0.0 vs 0 if (node[0] === 'num' && node[1].toString().indexOf('.') >= 0) return ASM_DOUBLE; if (node[0] === 'unary-prefix') return ASM_DOUBLE; + if (node[0] === 'call' && node[1][0] === 'name' && node[1][1] === 'Math_fround') return ASM_FLOAT; if (asmInfo && node[0] == 'name') return getAsmType(node[1], asmInfo); return ASM_INT; } function makeAsmCoercion(node, type) { - return type === ASM_INT ? ['binary', '|', node, ['num', 0]] : ['unary-prefix', '+', node]; + switch (type) { + case ASM_INT: return ['binary', '|', node, ['num', 0]]; + case ASM_DOUBLE: return ['unary-prefix', '+', node]; + case ASM_FLOAT: return ['call', ['name', 'Math_fround'], [node]]; + default: throw 'wha? ' + JSON.stringify([node, type]) + new Error().stack; + } } function makeAsmVarDef(v, type) { - return [v, type === ASM_INT ? ['num', 0] : ['unary-prefix', '+', ['num', 0]]]; + switch (type) { + case ASM_INT: return [v, ['num', 0]]; + case ASM_DOUBLE: return [v, ['unary-prefix', '+', ['num', 0]]]; + case ASM_FLOAT: return [v, ['call', ['name', 'Math_fround'], [['num', 0]]]]; + default: throw 'wha?'; + } } function getAsmType(name, asmInfo) { @@ -1568,7 +1580,8 @@ 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 + assert(value[0] === 'num' || (value[0] === 'unary-prefix' && value[2][0] === 'num') // must be valid coercion no-op + || (value[0] === 'call' && value[1][0] === 'name' && value[1][1] === 'Math_fround')); data.vars[name] = detectAsmCoercion(value); v.length = 1; // make an un-assigning var } else { @@ -3173,7 +3186,7 @@ function outline(ast) { var writes = {}; var namings = {}; - var hasReturn = false, hasReturnInt = false, hasReturnDouble = false, hasBreak = false, hasContinue = false; + var hasReturn = false, hasReturnType = {}, hasBreak = false, hasContinue = false; var breaks = {}; // set of labels we break or continue var continues = {}; // to (name -> id, just like labels) var breakCapturers = 0; @@ -3193,10 +3206,8 @@ function outline(ast) { } else if (type == 'return') { if (!node[1]) { hasReturn = true; - } else if (detectAsmCoercion(node[1]) == ASM_INT) { - hasReturnInt = true; } else { - hasReturnDouble = true; + hasReturnType[detectAsmCoercion(node[1])] = true; } } else if (type == 'break') { var label = node[1] || 0; @@ -3226,7 +3237,6 @@ function outline(ast) { continueCapturers--; } }); - assert(hasReturn + hasReturnInt + hasReturnDouble <= 1); var reads = {}; for (var v in namings) { @@ -3234,7 +3244,7 @@ function outline(ast) { if (actualReads > 0) reads[v] = actualReads; } - return { writes: writes, reads: reads, hasReturn: hasReturn, hasReturnInt: hasReturnInt, hasReturnDouble: hasReturnDouble, hasBreak: hasBreak, hasContinue: hasContinue, breaks: breaks, continues: continues, labels: labels }; + return { writes: writes, reads: reads, hasReturn: hasReturn, hasReturnType: hasReturnType, hasBreak: hasBreak, hasContinue: hasContinue, breaks: breaks, continues: continues, labels: labels }; } function makeAssign(dst, src) { @@ -3255,7 +3265,10 @@ function outline(ast) { return ['switch', value, cases]; } - var CONTROL_BREAK = 1, CONTROL_BREAK_LABEL = 2, CONTROL_CONTINUE = 3, CONTROL_CONTINUE_LABEL = 4, CONTROL_RETURN_VOID = 5, CONTROL_RETURN_INT = 6, CONTROL_RETURN_DOUBLE = 7; + var CONTROL_BREAK = 1, CONTROL_BREAK_LABEL = 2, CONTROL_CONTINUE = 3, CONTROL_CONTINUE_LABEL = 4, CONTROL_RETURN_VOID = 5, CONTROL_RETURN_INT = 6, CONTROL_RETURN_DOUBLE = 7, CONTROL_RETURN_FLOAT = 8; + function controlFromAsmType(asmType) { + return CONTROL_RETURN_INT + (asmType | 0); // assumes ASM_INT starts at 0, and order of these two is identical! + } var sizeToOutline = null; // customized per function and as we make progress function calculateThreshold(func, asmData) { @@ -3314,7 +3327,7 @@ function outline(ast) { }); // Generate new function - if (codeInfo.hasReturn || codeInfo.hasReturnInt || codeInfo.hasReturnDouble || codeInfo.hasBreak || codeInfo.hasContinue) { + if (codeInfo.hasReturn || codeInfo.hasReturnType[ASM_INT] || codeInfo.hasReturnType[ASM_DOUBLE] || codeInfo.hasReturnType[ASM_FLOAT] || codeInfo.hasBreak || codeInfo.hasContinue) { // we need to capture all control flow using a top-level labeled one-time loop in the outlined function var breakCapturers = 0; var continueCapturers = 0; @@ -3339,7 +3352,7 @@ function outline(ast) { ret.push(['stat', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos(outlineIndex)), ['num', CONTROL_RETURN_VOID])]); } else { var type = detectAsmCoercion(node[1], asmData); - ret.push(['stat', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos(outlineIndex)), ['num', type == ASM_INT ? CONTROL_RETURN_INT : CONTROL_RETURN_DOUBLE])]); + ret.push(['stat', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos(outlineIndex)), ['num', controlFromAsmType(type)])]); ret.push(['stat', makeAssign(makeStackAccess(type, asmData.controlDataStackPos(outlineIndex)), node[1])]); } ret.push(['stat', ['break', 'OL']]); @@ -3402,16 +3415,10 @@ function outline(ast) { [['stat', ['return']]] )); } - if (codeInfo.hasReturnInt) { + for (var returnType in codeInfo.hasReturnType) { reps.push(makeIf( - makeComparison(makeAsmCoercion(['name', 'tempValue'], ASM_INT), '==', ['num', CONTROL_RETURN_INT]), - [['stat', ['return', makeAsmCoercion(['name', 'tempInt'], ASM_INT)]]] - )); - } - if (codeInfo.hasReturnDouble) { - reps.push(makeIf( - makeComparison(makeAsmCoercion(['name', 'tempValue'], ASM_INT), '==', ['num', CONTROL_RETURN_DOUBLE]), - [['stat', ['return', makeAsmCoercion(['name', 'tempDouble'], ASM_DOUBLE)]]] + makeComparison(makeAsmCoercion(['name', 'tempValue'], ASM_INT), '==', ['num', controlFromAsmType(returnType)]), + [['stat', ['return', makeAsmCoercion(['name', 'tempInt'], returnType | 0)]]] )); } if (codeInfo.hasBreak) { @@ -3490,8 +3497,9 @@ function outline(ast) { var last = getStatements(func)[getStatements(func).length-1]; if (last[0] === 'stat') last = last[1]; if (last[0] !== 'return') { - if (allCodeInfo.hasReturnInt || allCodeInfo.hasReturnDouble) { - getStatements(func).push(['stat', ['return', makeAsmCoercion(['num', 0], allCodeInfo.hasReturnInt ? ASM_INT : ASM_DOUBLE)]]); + for (var returnType in codeInfo.hasReturnType) { + getStatements(func).push(['stat', ['return', makeAsmCoercion(['num', 0], returnType | 0)]]); + break; } } outliningParents[newIdent] = func[1]; From eb3ec20f8b8c78932d659ffdc6a1f5092a39d777 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 8 Nov 2013 18:18:12 -0800 Subject: [PATCH 32/61] fix registerize pass for float32 --- tools/js-optimizer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index cba3186115ef6..c69f7362d8889 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -1930,7 +1930,7 @@ function registerize(ast) { // we just use a fresh register to make sure we avoid this, but it could be // optimized to check for safe registers (free, and not used in this loop level). var varRegs = {}; // maps variables to the register they will use all their life - var freeRegsClasses = asm ? [[], []] : []; // two classes for asm, one otherwise + var freeRegsClasses = asm ? [[], [], []] : []; // two classes for asm, one otherwise XXX - hardcoded length var nextReg = 1; var fullNames = {}; var loopRegs = {}; // for each loop nesting level, the list of bound variables From 8d20a1424af064991bee3c66ef83ba0886b01180 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 8 Nov 2013 18:54:13 -0800 Subject: [PATCH 33/61] fix float/int bitcast optimization to recognize and preserve Math_fround --- tools/js-optimizer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index c69f7362d8889..54cde9f532bb2 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -618,6 +618,7 @@ function simplifyExpressions(ast) { if (asm) { if (hasTempDoublePtr) { + var asmData = normalizeAsm(ast); traverse(ast, function(node, type) { if (type === 'assign') { if (node[1] === true && node[2][0] === 'sub' && node[2][1][0] === 'name' && node[2][1][1] === 'HEAP32') { @@ -642,7 +643,7 @@ function simplifyExpressions(ast) { node[2][0] !== 'seq') { // avoid (x, y, z) which can be used for tempDoublePtr on doubles for alignment fixes if (node[1][2][1][1] === 'HEAP32') { node[1][3][1][1] = 'HEAPF32'; - return ['unary-prefix', '+', node[1][3]]; + return makeAsmCoercion(node[1][3], detectAsmCoercion(node[2])); } else { node[1][3][1][1] = 'HEAP32'; return ['binary', '|', node[1][3], ['num', 0]]; @@ -686,7 +687,6 @@ function simplifyExpressions(ast) { } } }); - var asmData = normalizeAsm(ast); for (var v in bitcastVars) { var info = bitcastVars[v]; // good variables define only one type, use only one type, have definitions and uses, and define as a different type than they use From 11418ef9f027c503e371da0bf82aff9387826594 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 8 Nov 2013 19:13:57 -0800 Subject: [PATCH 34/61] emit proper large float constants when js opts are to be run --- src/parseTools.js | 7 ++----- tests/cases/storebigfloat.ll | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 tests/cases/storebigfloat.ll diff --git a/src/parseTools.js b/src/parseTools.js index d25f9bd0c6a8e..088fea169ec0e 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1166,6 +1166,7 @@ function makeVarDef(js) { function ensureDot(value) { value = value.toString(); if (value.indexOf('.') >= 0 || /[IN]/.test(value)) return value; // if already dotted, or Infinity or NaN, nothing to do here + if (RUNNING_JS_OPTS) return '(+' + value + ')'; // JS optimizer will run, we must do +x, and it will be corrected later var e = value.indexOf('e'); if (e < 0) return value + '.0'; return value.substr(0, e) + '.0' + value.substr(e); @@ -1182,11 +1183,7 @@ function asmEnsureFloat(value, type) { // ensures that a float type has either 5 } // coerce if missing a '.', or if smaller than 1, so could be 1e-5 which has no . if (type in Runtime.FLOAT_TYPES && (value.toString().indexOf('.') < 0 || Math.abs(value) < 1)) { - if (RUNNING_JS_OPTS) { - return '(+' + value + ')'; // JS optimizer will run, we must do +x, and it will be corrected later - } else { - return ensureDot(value); - } + return ensureDot(value); } else { return value; } diff --git a/tests/cases/storebigfloat.ll b/tests/cases/storebigfloat.ll new file mode 100644 index 0000000000000..c9995835bec70 --- /dev/null +++ b/tests/cases/storebigfloat.ll @@ -0,0 +1,17 @@ + +@.str = private unnamed_addr constant [15 x i8] c"hello, world!\0A\00", align 1 ; [#uses=1 type=[15 x i8]*] + +; [#uses=0] +define i32 @main() { +entry: + %retval = alloca i32, align 4 ; [#uses=1 type=i32*] + %f = alloca float, align 4 + store float 1.000000e+10, float* %f, align 4 + store i32 0, i32* %retval + %call = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([15 x i8]* @.str, i32 0, i32 0)) ; [#uses=0 type=i32] + ret i32 1 +} + +; [#uses=1] +declare i32 @printf(i8*, ...) + From 8ccb00a3d0e8d0679553db1cdcc66027e59bc41c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sat, 9 Nov 2013 18:02:57 +0200 Subject: [PATCH 35/61] Fix test_lua on Windows when built using mingw32-make. That tool got confused by a '#' comment at the end of a command line, so move the comment to its own separate line. --- tests/lua/src/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/lua/src/Makefile b/tests/lua/src/Makefile index 401e73672a173..a9cf091105da1 100644 --- a/tests/lua/src/Makefile +++ b/tests/lua/src/Makefile @@ -59,8 +59,9 @@ o: $(ALL_O) a: $(ALL_A) +# XXX EMSCRIPTEN: add AR_ARGS $(LUA_A): $(BASE_O) - $(AR) $(AR_ARGS) $@ $(BASE_O) # XXX EMSCRIPTEN: add AR_ARGS + $(AR) $(AR_ARGS) $@ $(BASE_O) $(RANLIB) $@ $(LUA_T): $(LUA_O) $(LUA_A) From 7a3b033e450b94f5b8c32509fd9ec8b3ca539dfb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Sat, 9 Nov 2013 09:16:12 -0800 Subject: [PATCH 36/61] fix ensureDot logic --- src/parseTools.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/parseTools.js b/src/parseTools.js index 088fea169ec0e..adb3f6e4ff4a3 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1165,7 +1165,9 @@ function makeVarDef(js) { function ensureDot(value) { value = value.toString(); - if (value.indexOf('.') >= 0 || /[IN]/.test(value)) return value; // if already dotted, or Infinity or NaN, nothing to do here + // if already dotted, or Infinity or NaN, nothing to do here + // if smaller than 1 and running js opts, we always need to force a coercion (0.001 will turn into 1e-3, which has no .) + if ((value.indexOf('.') >= 0 || /[IN]/.test(value)) && (!RUNNING_JS_OPTS || Math.abs(value) >= 1)) return value; if (RUNNING_JS_OPTS) return '(+' + value + ')'; // JS optimizer will run, we must do +x, and it will be corrected later var e = value.indexOf('e'); if (e < 0) return value + '.0'; @@ -1181,8 +1183,7 @@ function asmEnsureFloat(value, type) { // ensures that a float type has either 5 value = ensureDot(value); return 'Math_fround(' + value + ')'; } - // coerce if missing a '.', or if smaller than 1, so could be 1e-5 which has no . - if (type in Runtime.FLOAT_TYPES && (value.toString().indexOf('.') < 0 || Math.abs(value) < 1)) { + if (type in Runtime.FLOAT_TYPES) { return ensureDot(value); } else { return value; From 7f5c2dec956c2d24b524583f80b23282d0c60353 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Sat, 9 Nov 2013 09:21:25 -0800 Subject: [PATCH 37/61] optimize hasSideEffects --- tools/js-optimizer.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 54cde9f532bb2..20d4d3d233c74 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -1145,10 +1145,12 @@ function simplifyNotComps(ast) { var NO_SIDE_EFFECTS = set('num', 'name'); function hasSideEffects(node) { // this is 99% incomplete! - if (node[0] in NO_SIDE_EFFECTS) return false; - if (node[0] === 'unary-prefix') return hasSideEffects(node[2]); - if (node[0] === 'binary') return hasSideEffects(node[2]) || hasSideEffects(node[3]); - return true; + switch (node[0]) { + case 'num': case 'name': return false; + case 'unary-prefix': return hasSideEffects(node[2]); + case 'binary': return hasSideEffects(node[2]) || hasSideEffects(node[3]); + default: return true; + } } // Clear out empty ifs and blocks, and redundant blocks/stats and so forth From e203c5b86c34c75e4b790bf2d22de926083d73ee Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Sat, 9 Nov 2013 09:22:24 -0800 Subject: [PATCH 38/61] remove unneeded var --- tools/js-optimizer.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 20d4d3d233c74..3e1b2b7f7401b 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -1142,8 +1142,6 @@ function simplifyNotComps(ast) { simplifyNotCompsPass = false; } -var NO_SIDE_EFFECTS = set('num', 'name'); - function hasSideEffects(node) { // this is 99% incomplete! switch (node[0]) { case 'num': case 'name': return false; From 16a5d12dc19b984cf3b55fc2682bd24f1d6834c4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Sat, 9 Nov 2013 09:29:34 -0800 Subject: [PATCH 39/61] consolidate side effect checking code --- tools/js-optimizer.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 3e1b2b7f7401b..8b05fc38e3baa 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -1144,9 +1144,10 @@ function simplifyNotComps(ast) { function hasSideEffects(node) { // this is 99% incomplete! switch (node[0]) { - case 'num': case 'name': return false; + case 'num': case 'name': case 'string': return false; case 'unary-prefix': return hasSideEffects(node[2]); case 'binary': return hasSideEffects(node[2]) || hasSideEffects(node[3]); + case 'sub': return hasSideEffects(node[1]) || hasSideEffects(node[2]); default: return true; } } @@ -2081,7 +2082,6 @@ function registerize(ast) { // can happen in ALLOW_MEMORY_GROWTH mode var ELIMINATION_SAFE_NODES = set('var', 'assign', 'call', 'if', 'toplevel', 'do', 'return', 'label', 'switch'); // do is checked carefully, however -var NODES_WITHOUT_ELIMINATION_SIDE_EFFECTS = set('name', 'num', 'string', 'binary', 'sub', 'unary-prefix'); var IGNORABLE_ELIMINATOR_SCAN_NODES = set('num', 'toplevel', 'string', 'break', 'continue', 'dot'); // dot can only be STRING_TABLE.* var ABORTING_ELIMINATOR_SCAN_NODES = set('new', 'object', 'function', 'defun', 'for', 'while', 'array', 'throw'); // we could handle some of these, TODO, but nontrivial (e.g. for while, the condition is hit multiple times after the body) @@ -2173,7 +2173,7 @@ function eliminate(ast, memSafe) { if (definitions[name] === 1 && uses[name] === 1) { potentials[name] = 1; } else if (uses[name] === 0 && (!definitions[name] || definitions[name] <= 1)) { // no uses, no def or 1 def (cannot operate on phis, and the llvm optimizer will remove unneeded phis anyhow) (no definition means it is a function parameter, or a local with just |var x;| but no defining assignment) - var hasSideEffects = false; + var sideEffects = false; var value = values[name]; if (value) { // TODO: merge with other side effect code @@ -2183,15 +2183,10 @@ function eliminate(ast, memSafe) { if (!(value[0] === 'seq' && value[1][0] === 'assign' && value[1][2][0] === 'sub' && value[1][2][2][0] === 'binary' && value[1][2][2][1] === '>>' && value[1][2][2][2][0] === 'name' && value[1][2][2][2][1] === 'tempDoublePtr')) { // If not that, then traverse and scan normally. - traverse(value, function(node, type) { - if (!(type in NODES_WITHOUT_ELIMINATION_SIDE_EFFECTS)) { - hasSideEffects = true; // cannot remove this unused variable, constructing it has side effects - return true; - } - }); + sideEffects = hasSideEffects(value); } } - if (!hasSideEffects) { + if (!sideEffects) { varsToRemove[name] = !definitions[name] ? 2 : 1; // remove it normally sideEffectFree[name] = true; // Each time we remove a variable with 0 uses, if its value has no From d420405180ee7910511c6a89572ce9f823880f3a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Sat, 9 Nov 2013 09:55:18 -0800 Subject: [PATCH 40/61] Math.* have no side effects --- tools/eliminator/eliminator-test-output.js | 3 ++ tools/eliminator/eliminator-test.js | 10 ++++++- tools/js-optimizer.js | 32 ++++++++++++++++------ 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/tools/eliminator/eliminator-test-output.js b/tools/eliminator/eliminator-test-output.js index 1a6506edd53d5..0171e99b12a8a 100644 --- a/tools/eliminator/eliminator-test-output.js +++ b/tools/eliminator/eliminator-test-output.js @@ -6119,4 +6119,7 @@ function intoCond() { HEAP32[$115 >> 2] = $NumWords; } } +function math(a, b, c, d) { + print(Math_imul(d) + (Math_fround(c) + (a + Math_abs(b)))); +} diff --git a/tools/eliminator/eliminator-test.js b/tools/eliminator/eliminator-test.js index ffad69ea5f06e..ef17b38892938 100644 --- a/tools/eliminator/eliminator-test.js +++ b/tools/eliminator/eliminator-test.js @@ -8852,5 +8852,13 @@ function intoCond() { HEAP32[$504 >> 2] = $503; } } -// EMSCRIPTEN_GENERATED_FUNCTIONS: ["a", "b", "c", "f", "g", "h", "py", "r", "t", "f2", "f3", "llvm3_1", "_inflate", "_malloc", "_mallocNoU", "asm", "phi", "intoCond"] +function math(a, b, c, d) { + var x, y, z, w; + x = a; + y = Math_abs(b); + z = Math_fround(c); + w = Math_imul(d); + print(x + y + z + w); +} +// EMSCRIPTEN_GENERATED_FUNCTIONS: ["a", "b", "c", "f", "g", "h", "py", "r", "t", "f2", "f3", "llvm3_1", "_inflate", "_malloc", "_mallocNoU", "asm", "phi", "intoCond", "math"] diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 8b05fc38e3baa..d2bfcd9185d21 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -1142,12 +1142,26 @@ function simplifyNotComps(ast) { simplifyNotCompsPass = false; } +function callHasSideEffects(node) { // checks if the call itself (not the args) has side effects (or is not statically known) + return !(node[1][0] === 'name' && /^Math_/.test(node[1][1])); +} + function hasSideEffects(node) { // this is 99% incomplete! switch (node[0]) { case 'num': case 'name': case 'string': return false; case 'unary-prefix': return hasSideEffects(node[2]); case 'binary': return hasSideEffects(node[2]) || hasSideEffects(node[3]); case 'sub': return hasSideEffects(node[1]) || hasSideEffects(node[2]); + case 'call': { + if (callHasSideEffects(node)) return true; + // This is a statically known call, with no side effects. only args can side effect us + var args = node[2]; + var num = args.length; + for (var i = 0; i < num; i++) { + if (hasSideEffects(args[i])) return true; + } + return false; + } default: return true; } } @@ -2445,14 +2459,16 @@ function eliminate(ast, memSafe) { for (var i = 0; i < args.length; i++) { traverseInOrder(args[i]); } - // these two invalidations will also invalidate calls - if (!globalsInvalidated) { - invalidateGlobals(); - globalsInvalidated = true; - } - if (!memoryInvalidated) { - invalidateMemory(); - memoryInvalidated = true; + if (callHasSideEffects(node)) { + // these two invalidations will also invalidate calls + if (!globalsInvalidated) { + invalidateGlobals(); + globalsInvalidated = true; + } + if (!memoryInvalidated) { + invalidateMemory(); + memoryInvalidated = true; + } } } else if (type === 'if') { if (allowTracking) { From c94fbc8db50b1bb8ba9c31bf6e93f2c733cac253 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Sat, 9 Nov 2013 10:34:06 -0800 Subject: [PATCH 41/61] optimize fround polyfill a tiny bit --- src/preamble.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/preamble.js b/src/preamble.js index 3238c99cb0ae2..3e76e5030454c 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -1076,8 +1076,10 @@ Math.imul = Math['imul']; #if PRECISE_F32 #if PRECISE_F32 == 1 -var froundBuffer = new Float32Array(1); -if (!Math['fround']) Math['fround'] = function(x) { froundBuffer[0] = x; return froundBuffer[0] }; +if (!Math['fround']) { + var froundBuffer = new Float32Array(1); + Math['fround'] = function(x) { froundBuffer[0] = x; return froundBuffer[0] }; +} #else // 2 if (!Math['fround']) Math['fround'] = function(x) { return x }; #endif From d969c64b05697855d6455863fac62b74991dd9e7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Sat, 9 Nov 2013 10:34:35 -0800 Subject: [PATCH 42/61] test both float and double in fasta --- tests/test_core.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index eec42c8a52113..b7b2f57e0ea5c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -8358,9 +8358,11 @@ def test_fasta(self): if self.emcc_args is None: return self.skip('requires emcc') results = [ (1,'''GG*ctt**tgagc*'''), (20,'''GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTT*cttBtatcatatgctaKggNcataaaSatgtaaaDcDRtBggDtctttataattcBgtcg**tacgtgtagcctagtgtttgtgttgcgttatagtctatttgtggacacagtatggtcaaa**tgacgtcttttgatctgacggcgttaacaaagatactctg*'''), (50,'''GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGA*TCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACAT*cttBtatcatatgctaKggNcataaaSatgtaaaDcDRtBggDtctttataattcBgtcg**tactDtDagcctatttSVHtHttKtgtHMaSattgWaHKHttttagacatWatgtRgaaa**NtactMcSMtYtcMgRtacttctWBacgaa**agatactctgggcaacacacatacttctctcatgttgtttcttcggacctttcataacct**ttcctggcacatggttagctgcacatcacaggattgtaagggtctagtggttcagtgagc**ggaatatcattcgtcggtggtgttaatctatctcggtgtagcttataaatgcatccgtaa**gaatattatgtttatttgtcggtacgttcatggtagtggtgtcgccgatttagacgtaaa**ggcatgtatg*''') ] - for i, j in results: - src = open(path_from_root('tests', 'fasta.cpp'), 'r').read() - self.do_run(src, j, [str(i)], lambda x, err: x.replace('\n', '*'), no_build=i>1) + for t in ['float', 'double']: + print t + src = open(path_from_root('tests', 'fasta.cpp'), 'r').read().replace('double', t) + for i, j in results: + self.do_run(src, j, [str(i)], lambda x, err: x.replace('\n', '*'), no_build=i>1) def test_whets(self): if not Settings.ASM_JS: return self.skip('mainly a test for asm validation here') From 23d8911d25700c246093849dac35377dc21a233f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sat, 9 Nov 2013 17:29:38 +0200 Subject: [PATCH 43/61] Fix test_asm_pgo on Windows by using response files. The test failed by exceeding Windows command line length limitations, giving an error "WindowsError: [Error 87] The parameter is incorrect". Also extend the logging given by EM_BUILD_VERBOSE=3 to print out the compiler invokations. --- tools/response_file.py | 6 ++++++ tools/shared.py | 21 ++++++++++----------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/tools/response_file.py b/tools/response_file.py index f19cf8af1a173..7f916752429c2 100644 --- a/tools/response_file.py +++ b/tools/response_file.py @@ -1,4 +1,5 @@ import tempfile, os, sys, shlex +import shared # Routes the given cmdline param list in args into a new response file and returns the filename to it. # The returned filename has a suffix '.rsp'. @@ -9,6 +10,11 @@ def create_response_file(args, directory): args = map(lambda p: p.replace('\\', '\\\\').replace('"', '\\"'), args) response_fd.write('"' + '" "'.join(args) + '"') response_fd.close() + + # Register the created .rsp file to be automatically cleaned up once this process finishes, so that + # caller does not have to remember to do it. + shared.configuration.get_temp_files().note(response_filename) + return response_filename # Reads a response file, and returns the list of cmdline params found in the file. diff --git a/tools/shared.py b/tools/shared.py index d38aef4cf1ae8..78d7e54ed4ca1 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -3,7 +3,7 @@ from tempfile import mkstemp from distutils.spawn import find_executable import jsrun, cache, tempfiles -from response_file import create_response_file +import response_file import logging, platform def listify(x): @@ -41,8 +41,8 @@ def __init__(self, args, bufsize=0, executable=None, stdin=None, stdout=None, st # emscripten.py supports reading args from a response file instead of cmdline. # Use .rsp to avoid cmdline length limitations on Windows. if len(args) >= 2 and args[1].endswith("emscripten.py"): - self.response_filename = create_response_file(args[2:], TEMP_DIR) - args = args[0:2] + ['@' + self.response_filename] + response_filename = response_file.create_response_file(args[2:], TEMP_DIR) + args = args[0:2] + ['@' + response_filename] try: # Call the process with fixed streams. @@ -78,13 +78,6 @@ def poll(self): def kill(self): return self.process.kill() - def __del__(self): - try: - # Clean up the temporary response file that was used to spawn this process, so that we don't leave temp files around. - tempfiles.try_delete(self.response_filename) - except: - pass # Mute all exceptions in dtor, particularly if we didn't use a response file, self.response_filename doesn't exist. - __rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) def path_from_root(*pathelems): return os.path.join(__rootpath__, *pathelems) @@ -1214,7 +1207,13 @@ def emscripten(filename, append_ext=True, extra_args=[]): # Run Emscripten Settings.RELOOPER = Cache.get_path('relooper.js') settings = Settings.serialize() - compiler_output = jsrun.timeout_run(Popen([PYTHON, EMSCRIPTEN, filename + ('.o.ll' if append_ext else ''), '-o', filename + '.o.js'] + settings + extra_args, stdout=PIPE), None, 'Compiling') + args = settings + extra_args + if WINDOWS: + args = ['@' + response_file.create_response_file(args, TEMP_DIR)] + cmdline = [PYTHON, EMSCRIPTEN, filename + ('.o.ll' if append_ext else ''), '-o', filename + '.o.js'] + args + if jsrun.TRACK_PROCESS_SPAWNS: + logging.info('Executing emscripten.py compiler with cmdline "' + ' '.join(cmdline) + '"') + compiler_output = jsrun.timeout_run(Popen(cmdline, stdout=PIPE), None, 'Compiling') #print compiler_output # Detect compilation crashes and errors From c4114043e9b5dea715b4a8b4328ab360ad3fd687 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Sat, 9 Nov 2013 11:43:09 -0800 Subject: [PATCH 44/61] make asm2f do full optimization, including minification --- tests/test_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_core.py b/tests/test_core.py index b7b2f57e0ea5c..4b76a9db81f6c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -10738,7 +10738,7 @@ def setUp(self): asm1 = make_run("asm1", compiler=CLANG, emcc_args=["-O1"]) asm1f = make_run("asm1f", compiler=CLANG, emcc_args=["-O1", "-s", "PRECISE_F32=1"]) asm2 = make_run("asm2", compiler=CLANG, emcc_args=["-O2"]) -asm2f = make_run("asm2f", compiler=CLANG, emcc_args=["-O2", "-s", "PRECISE_F32=1", "-g"]) # TODO: collapse into another test subsuite +asm2f = make_run("asm2f", compiler=CLANG, emcc_args=["-O2", "-s", "PRECISE_F32=1"]) # TODO: collapse into another test subsuite asm2g = make_run("asm2g", compiler=CLANG, emcc_args=["-O2", "-g", "-s", "ASSERTIONS=1", "--memory-init-file", "1", "-s", "CHECK_HEAP_ALIGN=1"]) asm2x86 = make_run("asm2x86", compiler=CLANG, emcc_args=["-O2", "-g", "-s", "CHECK_HEAP_ALIGN=1"], env={"EMCC_LLVM_TARGET": "i386-pc-linux-gnu"}) From d446e2b3c3b96474106b9bd47f35d901abb14f6d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Sat, 9 Nov 2013 16:18:31 -0800 Subject: [PATCH 45/61] support inline asm comments; fixes #1766 --- src/intertyper.js | 3 +++ src/parseTools.js | 6 ++++++ tests/test_core.py | 7 +++++++ 3 files changed, 16 insertions(+) diff --git a/src/intertyper.js b/src/intertyper.js index fceeb38d92b4a..fa53c6520d980 100644 --- a/src/intertyper.js +++ b/src/intertyper.js @@ -680,6 +680,9 @@ function intertyper(lines, sidePass, baseLineNums) { args.push(ident); }); } + item.ident = expandLLVMString(item.ident).replace(/(#[^\n]*)/g, function(m) { + return '/* ' + m.substr(1) + ' */'; // fix asm comments to js comments + }); if (item.assignTo) item.ident = 'return ' + item.ident; item.ident = '(function(' + params + ') { ' + item.ident + ' })(' + args + ');'; return { ret: item, item: item }; diff --git a/src/parseTools.js b/src/parseTools.js index c4f28184cb1cd..52727903a710a 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -981,6 +981,12 @@ function parseLLVMString(str) { return ret; } +function expandLLVMString(str) { + return str.replace(/\\../g, function(m) { + return String.fromCharCode(parseInt(m.substr(1), '16')); + }); +} + function getLabelIds(labels) { return labels.map(function(label) { return label.ident }); } diff --git a/tests/test_core.py b/tests/test_core.py index b2147d4936961..3680a92d3866d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3821,6 +3821,10 @@ def test_inlinejs(self): double get() { double ret = 0; __asm __volatile__("Math.abs(-12/3.3)":"=r"(ret)); // write to a variable + asm("#comment1"); + asm volatile("#comment2"); + asm volatile("#comment3\n" + "#comment4\n"); return ret; } @@ -3839,6 +3843,9 @@ def test_inlinejs(self): ''' self.do_run(src, 'Inline JS is very cool\n3.64\n') # TODO 1\n2\n3\n1\n2\n3\n') + if self.emcc_args == []: # opts will eliminate the comments + out = open('src.cpp.o.js').read() + for i in range(1, 5): assert ('comment%d' % i) in out def test_inlinejs2(self): if not self.is_le32(): return self.skip('le32 needed for inline js') From 159315a0edf8770305a1f042c30285fcc06d8663 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Sat, 9 Nov 2013 19:34:30 -0800 Subject: [PATCH 46/61] add asm2f to ALL --- tests/runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/runner.py b/tests/runner.py index 483df36e3f410..aed70d3511300 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -36,7 +36,7 @@ def path_from_root(*pathelems): # Core test runner class, shared between normal tests and benchmarks checked_sanity = False -test_modes = ['default', 'o1', 'o2', 'asm1', 'asm1f', 'asm2', 'asm2g', 'asm2x86', 's_0_0', 's_0_1'] +test_modes = ['default', 'o1', 'o2', 'asm1', 'asm1f', 'asm2', 'asm2f', 'asm2g', 'asm2x86', 's_0_0', 's_0_1'] test_index = 0 class RunnerCore(unittest.TestCase): From 39b138d4ddda29b535285bc1c5223641dcacce28 Mon Sep 17 00:00:00 2001 From: John Vilk Date: Wed, 6 Nov 2013 22:08:33 -0500 Subject: [PATCH 47/61] [SDL] Joystick API implementation using HTML5 Gamepad API Works in browsers that implement the working draft of the standard (current Chrome / Firefox stable): http://www.w3.org/TR/2012/WD-gamepad-20120529/#gamepad-interface ...and browsers that implement the editor's draft (current Firefox Nightly): https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#idl-def-Gamepad Contains unit tests for both event types. --- AUTHORS | 1 + src/library_sdl.js | 234 +++++++++++++++++++++++++++++++++++++++--- src/struct_info.json | 15 +++ tests/sdl_joystick.c | 123 ++++++++++++++++++++++ tests/test_browser.py | 76 ++++++++++++++ 5 files changed, 435 insertions(+), 14 deletions(-) create mode 100644 tests/sdl_joystick.c diff --git a/AUTHORS b/AUTHORS index 0b8fdf3370558..6a28c2054491d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -107,3 +107,4 @@ a license to everyone to use it as detailed in LICENSE.) * Michael Tirado * Ben Noordhuis * Bob Roberts +* John Vilk diff --git a/src/library_sdl.js b/src/library_sdl.js index 5b43b7aba53fa..c46364ff76f82 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -75,6 +75,7 @@ var LibrarySDL = { textInput: false, startTime: null, + initFlags: 0, // The flags passed to SDL_Init buttonState: 0, modState: 0, DOMButtons: [0, 0, 0], @@ -639,6 +640,21 @@ var LibrarySDL = { {{{ makeSetValue('ptr', C_STRUCTS.SDL_ResizeEvent.h, 'event.h', 'i32') }}}; break; } + case 'joystick_button_up': case 'joystick_button_down': { + var state = event.type === 'joystick_button_up' ? 0 : 1; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.which, 'event.index', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.button, 'event.button', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.state, 'state', 'i8') }}}; + break; + } + case 'joystick_axis_motion': { + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.which, 'event.index', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.axis, 'event.axis', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.value, 'SDL.joystickAxisValueConversion(event.value)', 'i32') }}}; + break; + } default: throw 'Unhandled SDL event: ' + event.type; } }, @@ -695,7 +711,109 @@ var LibrarySDL = { for (var i = 0; i < num; i++) { console.log(' diagonal ' + i + ':' + [data[i*surfData.width*4 + i*4 + 0], data[i*surfData.width*4 + i*4 + 1], data[i*surfData.width*4 + i*4 + 2], data[i*surfData.width*4 + i*4 + 3]]); } - } + }, + + // Joystick helper methods and state + + joystickEventState: 0, + lastJoystickState: {}, // Map from SDL_Joystick* to their last known state. Required to determine if a change has occurred. + // Maps Joystick names to pointers. Allows us to avoid reallocating memory for + // joystick names each time this function is called. + joystickNamePool: {}, + recordJoystickState: function(joystick, state) { + // Standardize button state. + var buttons = new Array(state.buttons.length); + for (var i = 0; i < state.buttons.length; i++) { + buttons[i] = SDL.getJoystickButtonState(state.buttons[i]); + } + + SDL.lastJoystickState[joystick] = { + buttons: buttons, + axes: state.axes.slice(0), + timestamp: state.timestamp, + index: state.index, + id: state.id + }; + }, + // Retrieves the button state of the given gamepad button. + // Abstracts away implementation differences. + // Returns 'true' if pressed, 'false' otherwise. + getJoystickButtonState: function(button) { + if (typeof button === 'object') { + // Current gamepad API editor's draft (Firefox Nightly) + // https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#idl-def-GamepadButton + return button.pressed; + } else { + // Current gamepad API working draft (Firefox / Chrome Stable) + // http://www.w3.org/TR/2012/WD-gamepad-20120529/#gamepad-interface + return button > 0; + } + }, + // Queries for and inserts controller events into the SDL queue. + queryJoysticks: function() { + for (var joystick in SDL.lastJoystickState) { + var state = SDL.getGamepad(joystick - 1); + var prevState = SDL.lastJoystickState[joystick]; + // Check only if the timestamp has differed. + // NOTE: Timestamp is not available in Firefox. + if (typeof state.timestamp !== 'number' || state.timestamp !== prevState.timestamp) { + var i; + for (i = 0; i < state.buttons.length; i++) { + var buttonState = SDL.getJoystickButtonState(state.buttons[i]); + // NOTE: The previous state already has a boolean representation of + // its button, so no need to standardize its button state here. + if (buttonState !== prevState.buttons[i]) { + // Insert button-press event. + SDL.events.push({ + type: buttonState ? 'joystick_button_down' : 'joystick_button_up', + joystick: joystick, + index: joystick - 1, + button: i + }); + } + } + for (i = 0; i < state.axes.length; i++) { + if (state.axes[i] !== prevState.axes[i]) { + // Insert axes-change event. + SDL.events.push({ + type: 'joystick_axis_motion', + joystick: joystick, + index: joystick - 1, + axis: i, + value: state.axes[i] + }); + } + } + + SDL.recordJoystickState(joystick, state); + } + } + }, + // Converts the double-based browser axis value [-1, 1] into SDL's 16-bit + // value [-32768, 32767] + joystickAxisValueConversion: function(value) { + // Ensures that 0 is 0, 1 is 32767, and -1 is 32768. + return Math.ceil(((value+1) * 32767.5) - 32768); + }, + + getGamepads: function() { + var fcn = navigator.getGamepads || navigator.webkitGamepads || navigator.mozGamepads || navigator.gamepads || navigator.webkitGetGamepads; + if (fcn !== undefined) { + // The function must be applied on the navigator object. + return fcn.apply(navigator); + } else { + return []; + } + }, + + // Helper function: Returns the gamepad if available, or null if not. + getGamepad: function(deviceIndex) { + var gamepads = SDL.getGamepads(); + if (gamepads.length > deviceIndex && deviceIndex >= 0) { + return gamepads[deviceIndex]; + } + return null; + }, }, SDL_Linked_Version: function() { @@ -708,8 +826,10 @@ var LibrarySDL = { return SDL.version; }, - SDL_Init: function(what) { + SDL_Init: function(initFlags) { SDL.startTime = Date.now(); + SDL.initFlags = initFlags; + // capture all key events. we just keep down and up, but also capture press to prevent default actions if (!Module['doNotCaptureKeyboard']) { document.addEventListener("keydown", SDL.receiveEvent); @@ -718,6 +838,15 @@ var LibrarySDL = { window.addEventListener("blur", SDL.receiveEvent); document.addEventListener("visibilitychange", SDL.receiveEvent); } + + if (initFlags & 0x200) { + // SDL_INIT_JOYSTICK + // Firefox will not give us Joystick data unless we register this NOP + // callback. + // https://bugzilla.mozilla.org/show_bug.cgi?id=936104 + addEventListener("gamepadconnected", function() {}); + } + window.addEventListener("unload", SDL.receiveEvent); SDL.keyboardState = _malloc(0x10000); // Our SDL needs 512, but 64K is safe for older SDLs _memset(SDL.keyboardState, 0, 0x10000); @@ -730,6 +859,12 @@ var LibrarySDL = { SDL.DOMEventToSDLEvent['mousemove'] = 0x400 /* SDL_MOUSEMOTION */; SDL.DOMEventToSDLEvent['unload'] = 0x100 /* SDL_QUIT */; SDL.DOMEventToSDLEvent['resize'] = 0x7001 /* SDL_VIDEORESIZE/SDL_EVENT_COMPAT2 */; + // These are not technically DOM events; the HTML gamepad API is poll-based. + // However, we define them here, as the rest of the SDL code assumes that + // all SDL events originate as DOM events. + SDL.DOMEventToSDLEvent['joystick_axis_motion'] = 0x600 /* SDL_JOYAXISMOTION */; + SDL.DOMEventToSDLEvent['joystick_button_down'] = 0x603 /* SDL_JOYBUTTONDOWN */; + SDL.DOMEventToSDLEvent['joystick_button_up'] = 0x604 /* SDL_JOYBUTTONUP */; return 0; // success }, @@ -1189,6 +1324,11 @@ var LibrarySDL = { }, SDL_PollEvent: function(ptr) { + if (SDL.initFlags & 0x200 && SDL.joystickEventState) { + // If SDL_INIT_JOYSTICK was supplied AND the joystick system is configured + // to automatically query for events, query for joystick events. + SDL.queryJoysticks(); + } if (SDL.events.length === 0) return 0; if (ptr) { SDL.makeCEvent(SDL.events.shift(), ptr); @@ -2375,37 +2515,103 @@ var LibrarySDL = { // Joysticks - SDL_NumJoysticks: function() { return 0; }, + SDL_NumJoysticks: function() { + var count = 0; + var gamepads = SDL.getGamepads(); + // The length is not the number of gamepads; check which ones are defined. + for (var i = 0; i < gamepads.length; i++) { + if (gamepads[i] !== undefined) count++; + } + return count; + }, - SDL_JoystickName: function(deviceIndex) { return 0; }, + SDL_JoystickName: function(deviceIndex) { + var gamepad = SDL.getGamepad(deviceIndex); + if (gamepad) { + var name = gamepad.id; + if (SDL.joystickNamePool.hasOwnProperty(name)) { + return SDL.joystickNamePool[name]; + } + return SDL.joystickNamePool[name] = allocate(intArrayFromString(name), 'i8', ALLOC_NORMAL); + } + return 0; + }, - SDL_JoystickOpen: function(deviceIndex) { return 0; }, + SDL_JoystickOpen: function(deviceIndex) { + var gamepad = SDL.getGamepad(deviceIndex); + if (gamepad) { + // Use this as a unique 'pointer' for this joystick. + var joystick = deviceIndex+1; + SDL.recordJoystickState(joystick, gamepad); + return joystick; + } + return 0; + }, - SDL_JoystickOpened: function(deviceIndex) { return 0; }, + SDL_JoystickOpened: function(deviceIndex) { + return SDL.lastJoystickState.hasOwnProperty(deviceIndex+1) ? 1 : 0; + }, - SDL_JoystickIndex: function(joystick) { return 0; }, + SDL_JoystickIndex: function(joystick) { + // joystick pointers are simply the deviceIndex+1. + return joystick - 1; + }, - SDL_JoystickNumAxes: function(joystick) { return 0; }, + SDL_JoystickNumAxes: function(joystick) { + var gamepad = SDL.getGamepad(joystick - 1); + if (gamepad) { + return gamepad.axes.length; + } + return 0; + }, SDL_JoystickNumBalls: function(joystick) { return 0; }, SDL_JoystickNumHats: function(joystick) { return 0; }, - SDL_JoystickNumButtons: function(joystick) { return 0; }, + SDL_JoystickNumButtons: function(joystick) { + var gamepad = SDL.getGamepad(joystick - 1); + if (gamepad) { + return gamepad.buttons.length; + } + return 0; + }, - SDL_JoystickUpdate: function() {}, + SDL_JoystickUpdate: function() { + SDL.queryJoysticks(); + }, - SDL_JoystickEventState: function(state) { return 0; }, + SDL_JoystickEventState: function(state) { + if (state < 0) { + // SDL_QUERY: Return current state. + return SDL.joystickEventState; + } + return SDL.joystickEventState = state; + }, - SDL_JoystickGetAxis: function(joystick, axis) { return 0; }, + SDL_JoystickGetAxis: function(joystick, axis) { + var gamepad = SDL.getGamepad(joystick - 1); + if (gamepad && gamepad.axes.length > axis) { + return SDL.joystickAxisValueConversion(gamepad.axes[axis]); + } + return 0; + }, SDL_JoystickGetHat: function(joystick, hat) { return 0; }, SDL_JoystickGetBall: function(joystick, ball, dxptr, dyptr) { return -1; }, - SDL_JoystickGetButton: function(joystick, button) { return 0; }, + SDL_JoystickGetButton: function(joystick, button) { + var gamepad = SDL.getGamepad(joystick - 1); + if (gamepad && gamepad.buttons.length > button) { + return SDL.getJoystickButtonState(gamepad.buttons[button]) ? 1 : 0; + } + return 0; + }, - SDL_JoystickClose: function(joystick) {}, + SDL_JoystickClose: function(joystick) { + delete SDL.lastJoystickState[joystick]; + }, // Misc diff --git a/src/struct_info.json b/src/struct_info.json index 5b4726e8003ee..f6499295313d7 100644 --- a/src/struct_info.json +++ b/src/struct_info.json @@ -961,6 +961,21 @@ "x", "y" ], + "SDL_JoyAxisEvent": [ + "type", + "which", + "axis", + "padding1", + "padding2", + "value" + ], + "SDL_JoyButtonEvent": [ + "type", + "which", + "button", + "state", + "padding1" + ], "SDL_ResizeEvent": [ "type", "w", diff --git a/tests/sdl_joystick.c b/tests/sdl_joystick.c new file mode 100644 index 0000000000000..7035050f8d840 --- /dev/null +++ b/tests/sdl_joystick.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include + +int result = 1; + +void assertJoystickEvent(int expectedGamepad, int expectedType, int expectedIndex, int expectedValue) { + SDL_Event event; + while(1) { + // Loop ends either when assertion fails (we run out of events), or we find + // the event we're looking for. + assert(SDL_PollEvent(&event) == 1); + if (event.type != expectedType) { + continue; + } + switch(event.type) { + case SDL_JOYAXISMOTION: { + assert(event.jaxis.which == expectedGamepad); + assert(event.jaxis.axis == expectedIndex); + assert(event.jaxis.value == expectedValue); + break; + } + case SDL_JOYBUTTONUP: case SDL_JOYBUTTONDOWN: { + assert(event.jbutton.which == expectedGamepad); + assert(event.jbutton.button == expectedIndex); + assert(event.jbutton.state == expectedValue); + break; + } + } + // Break out of while loop. + break; + } +} + +void assertNoJoystickEvent() { + SDL_Event event; + while(SDL_PollEvent(&event)) { + switch(event.type) { + case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: case SDL_JOYAXISMOTION: { + // Fail. + assert(0); + } + } + } +} + +void main_2(void* arg); + +int main() { + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK); + SDL_Surface *screen = SDL_SetVideoMode(600, 450, 32, SDL_HWSURFACE); + emscripten_async_call(main_2, NULL, 3000); // avoid startup delays and intermittent errors + return 0; +} + +void main_2(void* arg) { + // TODO: At the moment, we only support joystick support through polling. + emscripten_run_script("window.addNewGamepad('Pad Thai', 4, 16)"); + emscripten_run_script("window.addNewGamepad('Pad Kee Mao', 0, 4)"); + // Check that the joysticks exist properly. + assert(SDL_NumJoysticks() == 2); + assert(!SDL_JoystickOpened(0)); + assert(!SDL_JoystickOpened(1)); + SDL_Joystick* pad1 = SDL_JoystickOpen(0); + assert(SDL_JoystickOpened(0)); + assert(SDL_JoystickIndex(pad1) == 0); + assert(strncmp(SDL_JoystickName(0), "Pad Thai", 9) == 0); + assert(strncmp(SDL_JoystickName(1), "Pad Kee Mao", 12) == 0); + assert(SDL_JoystickNumAxes(pad1) == 4); + assert(SDL_JoystickNumButtons(pad1) == 16); + + // Button events. + emscripten_run_script("window.simulateGamepadButtonDown(0, 1)"); + // We didn't tell SDL to automatically update this joystick's state. + assertNoJoystickEvent(); + SDL_JoystickUpdate(); + assertJoystickEvent(0, SDL_JOYBUTTONDOWN, 1, SDL_PRESSED); + assert(SDL_JoystickGetButton(pad1, 1) == 1); + // Enable automatic updates. + SDL_JoystickEventState(SDL_ENABLE); + assert(SDL_JoystickEventState(SDL_QUERY) == SDL_ENABLE); + emscripten_run_script("window.simulateGamepadButtonUp(0, 1)"); + assertJoystickEvent(0, SDL_JOYBUTTONUP, 1, SDL_RELEASED); + assert(SDL_JoystickGetButton(pad1, 1) == 0); + // No button change: Should not result in a new event. + emscripten_run_script("window.simulateGamepadButtonUp(0, 1)"); + assertNoJoystickEvent(); + // Joystick 1 is not opened; should not result in a new event. + emscripten_run_script("window.simulateGamepadButtonDown(1, 1)"); + assertNoJoystickEvent(); + + // Joystick wiggling + emscripten_run_script("window.simulateAxisMotion(0, 0, 1)"); + assertJoystickEvent(0, SDL_JOYAXISMOTION, 0, 32767); + assert(SDL_JoystickGetAxis(pad1, 0) == 32767); + emscripten_run_script("window.simulateAxisMotion(0, 0, 0)"); + assertJoystickEvent(0, SDL_JOYAXISMOTION, 0, 0); + assert(SDL_JoystickGetAxis(pad1, 0) == 0); + emscripten_run_script("window.simulateAxisMotion(0, 1, -1)"); + assertJoystickEvent(0, SDL_JOYAXISMOTION, 1, -32768); + assert(SDL_JoystickGetAxis(pad1, 1) == -32768); + emscripten_run_script("window.simulateAxisMotion(0, 1, -1)"); + // No joystick change: Should not result in a new event. + assertNoJoystickEvent(); + // Joystick 1 is not opened; should not result in a new event. + emscripten_run_script("window.simulateAxisMotion(1, 1, -1)"); + assertNoJoystickEvent(); + + SDL_JoystickClose(pad1); + assert(!SDL_JoystickOpened(0)); + + // Joystick 0 is closed; we should not process any new gamepad events from it. + emscripten_run_script("window.simulateGamepadButtonDown(0, 1)"); + assertNoJoystickEvent(); + + // End test. + result = 2; + printf("Test passed!\n"); +} + diff --git a/tests/test_browser.py b/tests/test_browser.py index d52f109f478d1..1900e2cf91228 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -874,6 +874,82 @@ def test_glut_touchevents(self): def test_glut_wheelevents(self): self.btest('glut_wheelevents.c', '1') + def test_sdl_joystick_1(self): + # Generates events corresponding to the Working Draft of the HTML5 Gamepad API. + # http://www.w3.org/TR/2012/WD-gamepad-20120529/#gamepad-interface + open(os.path.join(self.get_dir(), 'pre.js'), 'w').write(''' + var gamepads = []; + // Spoof this function. + navigator['getGamepads'] = function() { + return gamepads; + }; + window['addNewGamepad'] = function(id, numAxes, numButtons) { + var index = gamepads.length; + gamepads.push({ + axes: new Array(numAxes), + buttons: new Array(numButtons), + id: id, + index: index + }); + var i; + for (i = 0; i < numAxes; i++) gamepads[index].axes[i] = 0; + for (i = 0; i < numButtons; i++) gamepads[index].buttons[i] = 0; + }; + window['simulateGamepadButtonDown'] = function (index, button) { + gamepads[index].buttons[button] = 1; + }; + window['simulateGamepadButtonUp'] = function (index, button) { + gamepads[index].buttons[button] = 0; + }; + window['simulateAxisMotion'] = function (index, axis, value) { + gamepads[index].axes[axis] = value; + }; + ''') + open(os.path.join(self.get_dir(), 'sdl_joystick.c'), 'w').write(self.with_report_result(open(path_from_root('tests', 'sdl_joystick.c')).read())) + + Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'sdl_joystick.c'), '-O2', '--minify', '0', '-o', 'page.html', '--pre-js', 'pre.js']).communicate() + self.run_browser('page.html', '', '/report_result?2') + + def test_sdl_joystick_2(self): + # Generates events corresponding to the Editor's Draft of the HTML5 Gamepad API. + # https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#idl-def-Gamepad + open(os.path.join(self.get_dir(), 'pre.js'), 'w').write(''' + var gamepads = []; + // Spoof this function. + navigator['getGamepads'] = function() { + return gamepads; + }; + window['addNewGamepad'] = function(id, numAxes, numButtons) { + var index = gamepads.length; + gamepads.push({ + axes: new Array(numAxes), + buttons: new Array(numButtons), + id: id, + index: index + }); + var i; + for (i = 0; i < numAxes; i++) gamepads[index].axes[i] = 0; + // Buttons are objects + for (i = 0; i < numButtons; i++) gamepads[index].buttons[i] = { pressed: false, value: 0 }; + }; + // FF mutates the original objects. + window['simulateGamepadButtonDown'] = function (index, button) { + gamepads[index].buttons[button].pressed = true; + gamepads[index].buttons[button].value = 1; + }; + window['simulateGamepadButtonUp'] = function (index, button) { + gamepads[index].buttons[button].pressed = false; + gamepads[index].buttons[button].value = 0; + }; + window['simulateAxisMotion'] = function (index, axis, value) { + gamepads[index].axes[axis] = value; + }; + ''') + open(os.path.join(self.get_dir(), 'sdl_joystick.c'), 'w').write(self.with_report_result(open(path_from_root('tests', 'sdl_joystick.c')).read())) + + Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'sdl_joystick.c'), '-O2', '--minify', '0', '-o', 'page.html', '--pre-js', 'pre.js']).communicate() + self.run_browser('page.html', '', '/report_result?2') + def test_webgl_context_attributes(self): # Javascript code to check the attributes support we want to test in the WebGL implementation # (request the attribute, create a context and check its value afterwards in the context attributes). From 1c7c811c8ded27a733075529f736797953a5209b Mon Sep 17 00:00:00 2001 From: ToadKing Date: Sun, 10 Nov 2013 03:38:58 -0500 Subject: [PATCH 48/61] nanosleep - don't write to rmtp if it's NULL --- src/library.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/library.js b/src/library.js index 31f531e931861..e08d63da6a393 100644 --- a/src/library.js +++ b/src/library.js @@ -6131,8 +6131,10 @@ LibraryManager.library = { // int nanosleep(const struct timespec *rqtp, struct timespec *rmtp); var seconds = {{{ makeGetValue('rqtp', C_STRUCTS.timespec.tv_sec, 'i32') }}}; var nanoseconds = {{{ makeGetValue('rqtp', C_STRUCTS.timespec.tv_nsec, 'i32') }}}; - {{{ makeSetValue('rmtp', C_STRUCTS.timespec.tv_sec, '0', 'i32') }}} - {{{ makeSetValue('rmtp', C_STRUCTS.timespec.tv_nsec, '0', 'i32') }}} + if (rmtp !== 0) { + {{{ makeSetValue('rmtp', C_STRUCTS.timespec.tv_sec, '0', 'i32') }}} + {{{ makeSetValue('rmtp', C_STRUCTS.timespec.tv_nsec, '0', 'i32') }}} + } return _usleep((seconds * 1e6) + (nanoseconds / 1000)); }, // TODO: Implement these for real. From 0d37bbee315eb06c9b326224d673bda0f4e9e338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 10 Nov 2013 14:48:56 +0200 Subject: [PATCH 49/61] Fix Makefile in test_lua in the case when make is in a path that contains spaces (e.g. c:\program files\emscripten\...\mingw32-make). --- tests/lua/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/lua/Makefile b/tests/lua/Makefile index bd9515fd84dad..9f0a2edde5a04 100644 --- a/tests/lua/Makefile +++ b/tests/lua/Makefile @@ -51,8 +51,9 @@ R= $V.1 # Targets start here. all: $(PLAT) +# XXX Emscripten Added quotes to $(MAKE) to properly call make when the path contains spaces $(PLATS) clean: - cd src && $(MAKE) $@ + cd src && "$(MAKE)" $@ test: dummy src/lua -v From 74e4c26aa1cdf71139b22ef79936a092706b5707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 10 Nov 2013 17:09:51 +0200 Subject: [PATCH 50/61] Make GL library more spec conformant by not throwing exceptions on errors, but instead reporting them via glGetError() as the GLES2 spec says. Use the linker flag "-s GL_ASSERTIONS=1" to enable additional debug-time error logging from the GL layer for error reasons. --- src/library_gl.js | 80 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 18 deletions(-) diff --git a/src/library_gl.js b/src/library_gl.js index ecb72f0f19e72..4b6ea579d5875 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -530,7 +530,11 @@ var LibraryGL = { ret = allocate(intArrayFromString('OpenGL ES GLSL 1.00 (WebGL)'), 'i8', ALLOC_NORMAL); break; default: - throw 'Failure: Invalid glGetString value: ' + name_; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetString: Unknown parameter ' + name_ + '!'); +#endif + return 0; } GL.stringCache[name_] = ret; return ret; @@ -561,7 +565,11 @@ var LibraryGL = { {{{ makeSetValue('p', '0', 'result ? 1 : 0', 'i8') }}}; break; case "string": - throw 'Native code calling glGetIntegerv(' + name_ + ') on a name which returns a string!'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetIntegerv: Native code calling glGetIntegerv(' + name_ + ') on a name which returns a string!'); +#endif + return; case "object": if (result === null) { {{{ makeSetValue('p', '0', '0', 'i32') }}}; @@ -583,13 +591,19 @@ var LibraryGL = { } else if (result instanceof WebGLTexture) { {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; } else { - throw 'Unknown object returned from WebGL getParameter'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetIntegerv: Unknown object returned from WebGL getParameter(' + name_ + ')!'); +#endif + return; } break; - case "undefined": - throw 'Native code calling glGetIntegerv(' + name_ + ') and it returns undefined'; default: - throw 'Why did we hit the default case?'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetIntegerv: Native code calling glGetIntegerv(' + name_ + ') and it returns ' + result + ' of type ' + typeof(result) + '!'); +#endif + return; } }, @@ -607,7 +621,11 @@ var LibraryGL = { {{{ makeSetValue('p', '0', '0', 'float') }}}; case "object": if (result === null) { - throw 'Native code calling glGetFloatv(' + name_ + ') and it returns null'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetFloatv: Native code calling glGetFloatv(' + name_ + ') and it returns null!'); +#endif + return; } else if (result instanceof Float32Array || result instanceof Uint32Array || result instanceof Int32Array || @@ -626,13 +644,19 @@ var LibraryGL = { } else if (result instanceof WebGLTexture) { {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; } else { - throw 'Unknown object returned from WebGL getParameter'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetFloatv: Native code calling glGetFloatv(' + name_ + ') and it returns ' + result + ' of type ' + typeof(result) + '!'); +#endif + return; } break; - case "undefined": - throw 'Native code calling glGetFloatv(' + name_ + ') and it returns undefined'; default: - throw 'Why did we hit the default case?'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetFloatv: Native code calling glGetFloatv(' + name_ + ') and it returns ' + result + ' of type ' + typeof(result) + '!'); +#endif + return; } }, @@ -647,7 +671,11 @@ var LibraryGL = { {{{ makeSetValue('p', '0', 'result != 0', 'i8') }}}; break; case "string": - throw 'Native code calling glGetBooleanv(' + name_ + ') on a name which returns a string!'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetBooleanv: Native code calling glGetBooleanv(' + name_ + ') on a name which returns a string!'); +#endif + return; case "object": if (result === null) { {{{ makeSetValue('p', '0', '0', 'i8') }}}; @@ -665,13 +693,19 @@ var LibraryGL = { result instanceof WebGLTexture) { {{{ makeSetValue('p', '0', '1', 'i8') }}}; // non-zero ID is always 1! } else { - throw 'Unknown object returned from WebGL getParameter'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetBooleanv: Unknown object returned from WebGL getParameter(' + name_ + ')!'); +#endif + return; } break; - case "undefined": - throw 'Unknown object returned from WebGL getParameter'; default: - throw 'Why did we hit the default case?'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetBooleanv: Native code calling glGetBooleanv(' + name_ + ') and it returns ' + result + ' of type ' + typeof(result) + '!'); +#endif + return; } }, @@ -759,7 +793,12 @@ var LibraryGL = { case 0x1908 /* GL_RGBA */: sizePerPixel = 4; break; - default: throw 'unsupported glReadPixels format'; + default: + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glReadPixels: Unsupported format ' + format + '!'); +#endif + return; } var totalSize = width*height*sizePerPixel; Module.ctx.readPixels(x, y, width, height, format, type, HEAPU8.subarray(pixels, pixels + totalSize)); @@ -2191,7 +2230,12 @@ var LibraryGL = { attribute = GLImmediate.clientAttributes[GLImmediate.COLOR]; break; case 0x8092: // GL_TEXTURE_COORD_ARRAY_POINTER attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0 + GLImmediate.clientActiveTexture]; break; - default: throw 'TODO: glGetPointerv for ' + name; + default: + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetPointerv: Unsupported name ' + name + '!'); +#endif + return; } {{{ makeSetValue('p', '0', 'attribute ? attribute.pointer : 0', 'i32') }}}; }, From eed2f8149d04b0fc991d3893f59a00f7f30442f0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Sun, 10 Nov 2013 11:34:32 -0800 Subject: [PATCH 51/61] more precise float testing --- tests/test_core.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 4b76a9db81f6c..8038ea83c9520 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -8358,11 +8358,14 @@ def test_fasta(self): if self.emcc_args is None: return self.skip('requires emcc') results = [ (1,'''GG*ctt**tgagc*'''), (20,'''GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTT*cttBtatcatatgctaKggNcataaaSatgtaaaDcDRtBggDtctttataattcBgtcg**tacgtgtagcctagtgtttgtgttgcgttatagtctatttgtggacacagtatggtcaaa**tgacgtcttttgatctgacggcgttaacaaagatactctg*'''), (50,'''GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGA*TCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACAT*cttBtatcatatgctaKggNcataaaSatgtaaaDcDRtBggDtctttataattcBgtcg**tactDtDagcctatttSVHtHttKtgtHMaSattgWaHKHttttagacatWatgtRgaaa**NtactMcSMtYtcMgRtacttctWBacgaa**agatactctgggcaacacacatacttctctcatgttgtttcttcggacctttcataacct**ttcctggcacatggttagctgcacatcacaggattgtaagggtctagtggttcagtgagc**ggaatatcattcgtcggtggtgttaatctatctcggtgtagcttataaatgcatccgtaa**gaatattatgtttatttgtcggtacgttcatggtagtggtgtcgccgatttagacgtaaa**ggcatgtatg*''') ] - for t in ['float', 'double']: - print t - src = open(path_from_root('tests', 'fasta.cpp'), 'r').read().replace('double', t) - for i, j in results: - self.do_run(src, j, [str(i)], lambda x, err: x.replace('\n', '*'), no_build=i>1) + for precision in [0, 1, 2]: + Settings.PRECISE_F32 = precision + for t in ['float', 'double']: + print precision, t + src = open(path_from_root('tests', 'fasta.cpp'), 'r').read().replace('double', t) + for i, j in results: + self.do_run(src, j, [str(i)], lambda x, err: x.replace('\n', '*'), no_build=i>1) + shutil.copyfile('src.cpp.o.js', '%d_%s.js' % (precision, t)) def test_whets(self): if not Settings.ASM_JS: return self.skip('mainly a test for asm validation here') From 37d0e6c55f9f48255b5ac34426196ddf6d5072a2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Sun, 10 Nov 2013 17:45:06 -0800 Subject: [PATCH 52/61] use writeAsciiToMemory in library.js in more places --- src/library.js | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/src/library.js b/src/library.js index e08d63da6a393..7a9546629a230 100644 --- a/src/library.js +++ b/src/library.js @@ -847,10 +847,7 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.ERANGE); return 0; } else { - for (var i = 0; i < cwd.length; i++) { - {{{ makeSetValue('buf', 'i', 'cwd.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('buf', 'i', '0', 'i8') }}} + writeAsciiToMemory(cwd, buf); return buf; } }, @@ -1293,10 +1290,7 @@ LibraryManager.library = { if (namesize < ret.length + 1) { return ___setErrNo(ERRNO_CODES.ERANGE); } else { - for (var i = 0; i < ret.length; i++) { - {{{ makeSetValue('name', 'i', 'ret.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('name', 'i', '0', 'i8') }}} + writeAsciiToMemory(ret, name); return 0; } }, @@ -2699,10 +2693,7 @@ LibraryManager.library = { var result = dir + '/' + name; if (!_tmpnam.buffer) _tmpnam.buffer = _malloc(256); if (!s) s = _tmpnam.buffer; - for (var i = 0; i < result.length; i++) { - {{{ makeSetValue('s', 'i', 'result.charCodeAt(i)', 'i8') }}}; - } - {{{ makeSetValue('s', 'i', '0', 'i8') }}}; + writeAsciiToMemory(result, s); return s; }, tempnam__deps: ['tmpnam'], @@ -3343,10 +3334,7 @@ LibraryManager.library = { var ptrSize = {{{ Runtime.getNativeTypeSize('i8*') }}}; for (var i = 0; i < strings.length; i++) { var line = strings[i]; - for (var j = 0; j < line.length; j++) { - {{{ makeSetValue('poolPtr', 'j', 'line.charCodeAt(j)', 'i8') }}}; - } - {{{ makeSetValue('poolPtr', 'j', '0', 'i8') }}}; + writeAsciiToMemory(line, poolPtr); {{{ makeSetValue('envPtr', 'i * ptrSize', 'poolPtr', 'i8*') }}}; poolPtr += line.length + 1; } @@ -3976,10 +3964,7 @@ LibraryManager.library = { return ___setErrNo(ERRNO_CODES.ERANGE); } else { var msg = ERRNO_MESSAGES[errnum]; - for (var i = 0; i < msg.length; i++) { - {{{ makeSetValue('strerrbuf', 'i', 'msg.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('strerrbuf', 'i', 0, 'i8') }}} + writeAsciiToMemory(msg, strerrbuf); return 0; } } else { @@ -5067,10 +5052,7 @@ LibraryManager.library = { var layout = {{{ JSON.stringify(C_STRUCTS.utsname) }}}; function copyString(element, value) { var offset = layout[element]; - for (var i = 0; i < value.length; i++) { - {{{ makeSetValue('name', 'offset + i', 'value.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('name', 'offset + i', '0', 'i8') }}} + writeAsciiToMemory(value, name + offset); } if (name === 0) { return -1; @@ -6574,10 +6556,7 @@ LibraryManager.library = { var me = _nl_langinfo; if (!me.ret) me.ret = _malloc(32); - for (var i = 0; i < result.length; i++) { - {{{ makeSetValue('me.ret', 'i', 'result.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('me.ret', 'i', '0', 'i8') }}} + writeAsciiToMemory(result, me.ret); return me.ret; }, From eb8172ed7872c99b6438c4ab0fa9f83372fc44b0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 11 Nov 2013 10:06:29 -0800 Subject: [PATCH 53/61] minify in asm var definitions that denormalizeAsm generates, to handle Math.fround calls --- tools/js-optimizer.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index d2bfcd9185d21..0371b3a374b6c 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -2059,6 +2059,25 @@ function registerize(ast) { } } denormalizeAsm(fun, finalAsmData); + if (extraInfo && extraInfo.globals) { + // minify in asm var definitions, that denormalizeAsm just generated + var stats = fun[3]; + for (var i = fun[2].length; i < stats.length; i++) { + var line = stats[i]; + if (line[0] !== 'var') break; + var pairs = line[1]; + for (var j = 0; j < pairs.length; j++) { + var value = pairs[j][1]; + if (value && value[0] === 'call' && value[1][0] === 'name') { + var name = value[1][1]; + var minified = extraInfo.globals[name]; + if (minified) { + value[1][1] = minified; + } + } + } + } + } } }); } From e238b7e11fa973cd86721e5b1ed45b50b52e3c0d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 11 Nov 2013 10:23:40 -0800 Subject: [PATCH 54/61] minify in asm arg coercions that denormalizeAsm generates, to handle Math.fround calls --- tools/js-optimizer.js | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 0371b3a374b6c..57ce0071d5e6f 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -2061,19 +2061,27 @@ function registerize(ast) { denormalizeAsm(fun, finalAsmData); if (extraInfo && extraInfo.globals) { // minify in asm var definitions, that denormalizeAsm just generated + function minify(value) { + if (value && value[0] === 'call' && value[1][0] === 'name') { + var name = value[1][1]; + var minified = extraInfo.globals[name]; + if (minified) { + value[1][1] = minified; + } + } + } var stats = fun[3]; - for (var i = fun[2].length; i < stats.length; i++) { + for (var i = 0; i < stats.length; i++) { var line = stats[i]; - if (line[0] !== 'var') break; - var pairs = line[1]; - for (var j = 0; j < pairs.length; j++) { - var value = pairs[j][1]; - if (value && value[0] === 'call' && value[1][0] === 'name') { - var name = value[1][1]; - var minified = extraInfo.globals[name]; - if (minified) { - value[1][1] = minified; - } + if (i >= fun[2].length && line[0] !== 'var') break; // when we pass the arg and var coercions, break + if (line[0] === 'stat') { + assert(line[1][0] === 'assign'); + minify(line[1][3]); + } else { + assert(line[0] === 'var'); + var pairs = line[1]; + for (var j = 0; j < pairs.length; j++) { + minify(pairs[j][1]); } } } From fa0443ee8ef4306ee2bf848a6c18d189bafbac77 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 11 Nov 2013 10:41:01 -0800 Subject: [PATCH 55/61] benchmark linpack in both float and double modes --- tests/test_benchmark.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py index e9cfee52df082..1c5a2d71601ab 100644 --- a/tests/test_benchmark.py +++ b/tests/test_benchmark.py @@ -428,10 +428,15 @@ def test_life(self): src = open(path_from_root('tests', 'life.c'), 'r').read() self.do_benchmark('life', src, '''--------------------------------''', shared_args=['-std=c99'], force_c=True) - def test_linpack(self): + def test_linpack_double(self): def output_parser(output): return 100.0/float(re.search('Unrolled Double Precision +([\d\.]+) Mflops', output).group(1)) - self.do_benchmark('linpack', open(path_from_root('tests', 'linpack.c')).read(), '''Unrolled Double Precision''', force_c=True, output_parser=output_parser) + self.do_benchmark('linpack_double', open(path_from_root('tests', 'linpack.c')).read(), '''Unrolled Double Precision''', force_c=True, output_parser=output_parser) + + def test_linpack_float(self): + def output_parser(output): + return 100.0/float(re.search('Unrolled Single Precision +([\d\.]+) Mflops', output).group(1)) + self.do_benchmark('linpack_float', open(path_from_root('tests', 'linpack.c')).read(), '''Unrolled Single Precision''', force_c=True, output_parser=output_parser, shared_args=['-DSP']) def test_zzz_java_nbody(self): # tests xmlvm compiled java, including bitcasts of doubles, i64 math, etc. args = [path_from_root('tests', 'nbody-java', x) for x in os.listdir(path_from_root('tests', 'nbody-java')) if x.endswith('.c')] + \ @@ -504,4 +509,4 @@ def test_zzz_bullet(self): # Called thus so it runs late in the alphabetical cyc native_args = native_lib + ['-I' + path_from_root('tests', 'bullet', 'src'), '-I' + path_from_root('tests', 'bullet', 'Demos', 'Benchmarks')] - self.do_benchmark('bullet', src, '\nok.\n', emcc_args=emcc_args, native_args=native_args) \ No newline at end of file + self.do_benchmark('bullet', src, '\nok.\n', emcc_args=emcc_args, native_args=native_args) From 9d006256df96e6d81bf869958af28643720f14db Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 11 Nov 2013 10:59:18 -0800 Subject: [PATCH 56/61] coerce to double before converting a float to an i64 --- src/parseTools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parseTools.js b/src/parseTools.js index adb3f6e4ff4a3..e806c86111b6b 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -2259,7 +2259,7 @@ function processMathop(item) { throw 'shifts should have been legalized!'; } case 'uitofp': case 'sitofp': return makeFloat(RuntimeGenerator.makeBigInt(low1, high1, op[0] == 'u'), item.type); - case 'fptoui': case 'fptosi': return finish(splitI64(idents[0], true)); + case 'fptoui': case 'fptosi': return finish(splitI64(asmCoercion(idents[0], 'double'), true)); // coerce to double before conversion to i64 case 'icmp': { switch (variant) { case 'uge': return '((' + high1 + '>>>0) >= (' + high2 + '>>>0)) & ((((' + high1 + '>>>0) > (' + high2 + '>>>0)) | ' + From 0ba049d69dbcb0cafd339f33eaa9dc9aa292a7e1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 11 Nov 2013 10:59:55 -0800 Subject: [PATCH 57/61] add option for precise f32 in benchmarks --- tests/test_benchmark.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py index 1c5a2d71601ab..943b133d63975 100644 --- a/tests/test_benchmark.py +++ b/tests/test_benchmark.py @@ -122,6 +122,7 @@ def process(filename): '--llvm-lto', '3', '--memory-init-file', '0', '--js-transform', 'python hardcode.py', '-s', 'TOTAL_MEMORY=128*1024*1024', '--closure', '1', + #'-s', 'PRECISE_F32=1', #'-g', '-o', final_filename] + shared_args + emcc_args, stdout=PIPE, stderr=self.stderr_redirect).communicate() assert os.path.exists(final_filename), 'Failed to compile file: ' + output[0] From 2182170ca32698886e311b7e5edbb5f9ed30aa3a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 11 Nov 2013 11:29:47 -0800 Subject: [PATCH 58/61] test suite changes for f32 landing: enable float32_precise in all modes, disable asm1f (rely on testing in float32_precise and fasta), and mark asm2f as permanent --- tests/test_core.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 8038ea83c9520..30b9c70f4d08f 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -883,7 +883,7 @@ def test_double_i64_conversion(self): self.do_run(src, 'OK!\n'); def test_float32_precise(self): - if self.emcc_args == None or 'PRECISE_F32=1' not in self.emcc_args: return self.skip('needs precise float32') + Settings.PRECISE_F32 = 1 src = r''' #include @@ -10739,9 +10739,8 @@ def setUp(self): # asm.js asm1 = make_run("asm1", compiler=CLANG, emcc_args=["-O1"]) -asm1f = make_run("asm1f", compiler=CLANG, emcc_args=["-O1", "-s", "PRECISE_F32=1"]) asm2 = make_run("asm2", compiler=CLANG, emcc_args=["-O2"]) -asm2f = make_run("asm2f", compiler=CLANG, emcc_args=["-O2", "-s", "PRECISE_F32=1"]) # TODO: collapse into another test subsuite +asm2f = make_run("asm2f", compiler=CLANG, emcc_args=["-O2", "-s", "PRECISE_F32=1"]) asm2g = make_run("asm2g", compiler=CLANG, emcc_args=["-O2", "-g", "-s", "ASSERTIONS=1", "--memory-init-file", "1", "-s", "CHECK_HEAP_ALIGN=1"]) asm2x86 = make_run("asm2x86", compiler=CLANG, emcc_args=["-O2", "-g", "-s", "CHECK_HEAP_ALIGN=1"], env={"EMCC_LLVM_TARGET": "i386-pc-linux-gnu"}) From 0ea99d4659ec686115c70f0b7e67fa829739c1e0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 11 Nov 2013 11:47:05 -0800 Subject: [PATCH 59/61] remove asm1f from ALL --- tests/runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/runner.py b/tests/runner.py index aed70d3511300..8c4a9abf448db 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -36,7 +36,7 @@ def path_from_root(*pathelems): # Core test runner class, shared between normal tests and benchmarks checked_sanity = False -test_modes = ['default', 'o1', 'o2', 'asm1', 'asm1f', 'asm2', 'asm2f', 'asm2g', 'asm2x86', 's_0_0', 's_0_1'] +test_modes = ['default', 'o1', 'o2', 'asm1', 'asm2', 'asm2f', 'asm2g', 'asm2x86', 's_0_0', 's_0_1'] test_index = 0 class RunnerCore(unittest.TestCase): From 7e25db4555f3ce93014340a22b4901a2f379b0bc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 11 Nov 2013 15:52:34 -0800 Subject: [PATCH 60/61] remove unnecessary ltoing in benchmark suite --- tests/test_benchmark.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py index 943b133d63975..d19afb8d6a89e 100644 --- a/tests/test_benchmark.py +++ b/tests/test_benchmark.py @@ -119,7 +119,7 @@ def process(filename): try_delete(final_filename) output = Popen([PYTHON, EMCC, filename, #'-O3', '-O2', '-s', 'DOUBLE_MODE=0', '-s', 'PRECISE_I64_MATH=0', - '--llvm-lto', '3', '--memory-init-file', '0', '--js-transform', 'python hardcode.py', + '--memory-init-file', '0', '--js-transform', 'python hardcode.py', '-s', 'TOTAL_MEMORY=128*1024*1024', '--closure', '1', #'-s', 'PRECISE_F32=1', From 9e17b54163e9888e31bf79cb2eee7df10e5ee06a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 11 Nov 2013 17:40:43 -0800 Subject: [PATCH 61/61] version 1.7.3 --- tools/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/shared.py b/tools/shared.py index a2654bef85eb0..03a22f789650b 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -307,7 +307,7 @@ def find_temp_directory(): # we re-check sanity when the settings are changed) # We also re-check sanity and clear the cache when the version changes -EMSCRIPTEN_VERSION = '1.7.2' +EMSCRIPTEN_VERSION = '1.7.3' def generate_sanity(): return EMSCRIPTEN_VERSION + '|' + get_llvm_target() + '|' + LLVM_ROOT