diff options
50 files changed, 2142 insertions, 930 deletions
@@ -471,6 +471,9 @@ Options that are modified or new in %s include: to hide these warnings and acknowledge that the explicit use of absolute paths is intentional. + --proxy-to-worker Generates both html and js files. The main + program is in js, and the html proxies to/from it. + The target file, if specified (-o <target>), defines what will be generated: @@ -740,6 +743,7 @@ try: save_bc = False memory_init_file = False use_preload_cache = False + proxy_to_worker = False if use_cxx: default_cxx_std = '-std=c++03' # Enforce a consistent C++ standard when compiling .cpp files, if user does not specify one on the cmdline. @@ -903,6 +907,9 @@ try: memory_init_file = int(newargs[i+1]) newargs[i] = '' newargs[i+1] = '' + elif newargs[i] == '--proxy-to-worker': + proxy_to_worker = True + newargs[i] = '' elif newargs[i].startswith(('-I', '-L')): path_name = newargs[i][2:] if not absolute_warning_shown and os.path.isabs(path_name): @@ -1125,6 +1132,12 @@ try: else: logging.debug('using response file for EXPORTED_FUNCTIONS, make sure it includes _malloc and _free') + if shared.Settings.ASM_JS and shared.Settings.DLOPEN_SUPPORT: + assert shared.Settings.DISABLE_EXCEPTION_CATCHING, 'no exceptions support with dlopen in asm yet' + + if proxy_to_worker: + shared.Settings.PROXY_TO_WORKER = 1 + ## Compile source code to bitcode logging.debug('compiling to bitcode') @@ -1706,7 +1719,11 @@ try: logging.debug('generating HTML') shell = open(shell_path).read() html = open(target, 'w') - if not Compression.on: + if proxy_to_worker: + html.write(shell.replace('{{{ SCRIPT_CODE }}}', open(shared.path_from_root('src', 'proxyClient.js')).read().replace('{{{ filename }}}', target_basename))) + js_target = unsuffixed(target) + '.js' + shutil.copyfile(final, js_target) + elif not Compression.on: if debug_level >= 4: match = re.match('.*?<script[^>]*>{{{ SCRIPT_CODE }}}</script>', shell, re.DOTALL) @@ -1780,13 +1797,12 @@ try: html.close() else: if split_js_file: - from tools.split import split_javascript_file - split_javascript_file(final, unsuffixed(target), split_js_file) + from tools.split import split_javascript_file + split_javascript_file(final, unsuffixed(target), split_js_file) else: - if debug_level >= 4: generate_source_map(target) - - # copy final JS to output - shutil.move(final, target) + if debug_level >= 4: generate_source_map(target) + # copy final JS to output + shutil.move(final, target) if DEBUG: logging.debug('total time: %.2f seconds' % (time.time() - start_time)) diff --git a/emlibtool b/emlibtool deleted file mode 100755 index 1eb18edc..00000000 --- a/emlibtool +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python2 - -''' -This is a helper script. See emcc. -''' - -import os, sys -from tools import shared - -raise Exception('TODO: emlibtool') - diff --git a/emlibtool.bat b/emlibtool.bat deleted file mode 100644 index 4ea705be..00000000 --- a/emlibtool.bat +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -python "%~dp0\emlibtool" %*
\ No newline at end of file diff --git a/emscripten.py b/emscripten.py index 257527fe..fcf109bf 100755 --- a/emscripten.py +++ b/emscripten.py @@ -287,6 +287,7 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, exported_implemented_functions = set() for func_js, curr_forwarded_data in outputs: curr_forwarded_json = json.loads(curr_forwarded_data) + forwarded_json['Types']['hasInlineJS'] = forwarded_json['Types']['hasInlineJS'] or curr_forwarded_json['Types']['hasInlineJS'] forwarded_json['Types']['preciseI64MathUsed'] = forwarded_json['Types']['preciseI64MathUsed'] or curr_forwarded_json['Types']['preciseI64MathUsed'] for key, value in curr_forwarded_json['Functions']['blockAddresses'].iteritems(): forwarded_json['Functions']['blockAddresses'][key] = value @@ -543,7 +544,7 @@ function asmPrintFloat(x, y) { } // EMSCRIPTEN_START_ASM var asm = (function(global, env, buffer) { - 'use asm'; + %s var HEAP8 = new global.Int8Array(buffer); var HEAP16 = new global.Int16Array(buffer); var HEAP32 = new global.Int32Array(buffer); @@ -552,7 +553,7 @@ var asm = (function(global, env, buffer) { var HEAPU32 = new global.Uint32Array(buffer); var HEAPF32 = new global.Float32Array(buffer); var HEAPF64 = new global.Float64Array(buffer); -''' % (asm_setup,) + '\n' + asm_global_vars + ''' +''' % (asm_setup, "'use asm';" if not forwarded_json['Types']['hasInlineJS'] and not settings['SIDE_MODULE'] else "'almost asm';") + '\n' + asm_global_vars + ''' var __THREW__ = 0; var threwValue = 0; var setjmpId = 0; diff --git a/src/intertyper.js b/src/intertyper.js index 31e97bd0..f9633549 100644 --- a/src/intertyper.js +++ b/src/intertyper.js @@ -707,16 +707,25 @@ function intertyper(data, sidePass, baseLineNums) { var tokensLeft = item.tokens.slice(2); item.ident = eatLLVMIdent(tokensLeft); if (item.ident == 'asm') { + if (ASM_JS) { + Types.hasInlineJS = true; + warnOnce('inline JavaScript (asm, EM_ASM) will cause the code to no longer fall in the asm.js subset of JavaScript, which can reduce performance'); + } + assert(TARGET_LE32, 'inline js is only supported in le32'); // Inline assembly is just JavaScript that we paste into the code item.intertype = 'value'; if (tokensLeft[0].text == 'sideeffect') tokensLeft.splice(0, 1); item.ident = tokensLeft[0].text.substr(1, tokensLeft[0].text.length-2) || ';'; // use ; for empty inline assembly var i = 0; + var params = [], args = []; splitTokenList(tokensLeft[3].item.tokens).map(function(element) { var ident = toNiceIdent(element[1].text); var type = element[0].text; - item.ident = item.ident.replace(new RegExp('\\$' + i++, 'g'), ident); + params.push('$' + (i++)); + args.push(ident); }); + if (item.assignTo) item.ident = 'return ' + item.ident; + item.ident = '(function(' + params + ') { ' + item.ident + ' })(' + args + ');'; return { forward: null, ret: [item], item: item }; } if (item.ident.substr(-2) == '()') { diff --git a/src/jsifier.js b/src/jsifier.js index 8592364d..a3b26aa9 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -324,11 +324,13 @@ function JSify(data, functionsOnly, givenFunctions) { assert(typeof constant === 'object');//, [typeof constant, JSON.stringify(constant), item.external]); // This is a flattened object. We need to find its idents, so they can be assigned to later + var structTypes = null; constant.forEach(function(value, i) { if (needsPostSet(value)) { // ident, or expression containing an ident + if (!structTypes) structTypes = generateStructTypes(item.type); ret.push({ intertype: 'GlobalVariablePostSet', - JS: makeSetValue(makeGlobalUse(item.ident), i, value, 'i32', false, true) + ';' // ignore=true, since e.g. rtti and statics cause lots of safe_heap errors + JS: makeSetValue(makeGlobalUse(item.ident), i, value, structTypes[i], false, true) + ';' // ignore=true, since e.g. rtti and statics cause lots of safe_heap errors }); constant[i] = '0'; } @@ -338,6 +340,7 @@ function JSify(data, functionsOnly, givenFunctions) { // External variables in shared libraries should not be declared as // they would shadow similarly-named globals in the parent, so do nothing here. if (BUILD_AS_SHARED_LIB) return ret; + if (SIDE_MODULE) return []; // Library items need us to emit something, but everything else requires nothing. if (!LibraryManager.library[item.ident.slice(1)]) return ret; } @@ -1142,8 +1145,8 @@ function JSify(data, functionsOnly, givenFunctions) { }); var range = maxx - minn; var useIfs = (item.switchLabels.length+1) < 6 || range > 10*1024 || (range/item.switchLabels.length) > 1024; // heuristics - if (VERBOSE && useIfs && item.switchLabels.length > 2) { - warn('not optimizing llvm switch into js switch because ' + [range, range/item.switchLabels.length]); + if (VERBOSE && useIfs && item.switchLabels.length >= 6) { + warn('not optimizing llvm switch into js switch because range of values is ' + range + ', density is ' + range/item.switchLabels.length); } var phiSets = calcPhiSets(item); @@ -1408,7 +1411,7 @@ function JSify(data, functionsOnly, givenFunctions) { var extCall = false; if (ASM_JS && funcData.setjmpTable) forceByPointer = true; // in asm.js mode, we must do an invoke for each call - if (ASM_JS && DLOPEN_SUPPORT && !invoke) extCall = true; // go out, to be able to access other modules TODO: optimize + if (ASM_JS && DLOPEN_SUPPORT && !invoke && !funcData.setjmpTable) extCall = true; // go out, to be able to access other modules TODO: optimize ident = Variables.resolveAliasToIdent(ident); var shortident = ident.slice(1); @@ -1840,6 +1843,9 @@ function JSify(data, functionsOnly, givenFunctions) { print(read('headless.js').replace("'%s'", "'http://emscripten.org'").replace("'?%s'", "''").replace("'?%s'", "'/'").replace('%s,', 'null,').replace('%d', '0')); print('}'); } + if (PROXY_TO_WORKER) { + print(read('proxyWorker.js')); + } if (RUNTIME_TYPE_INFO) { Types.cleanForRuntime(); print('Runtime.typeInfo = ' + JSON.stringify(Types.types)); diff --git a/src/library.js b/src/library.js index 7894e033..f3c3c1ec 100644 --- a/src/library.js +++ b/src/library.js @@ -323,10 +323,16 @@ LibraryManager.library = { path = Pointer_stringify(path); // we don't want this in the JS API as the JS API // uses mknod to create all nodes. - var err = FS.mayMknod(mode); - if (err) { - ___setErrNo(err); - return -1; + switch (mode & {{{ cDefine('S_IFMT') }}}) { + case {{{ cDefine('S_IFREG') }}}: + case {{{ cDefine('S_IFCHR') }}}: + case {{{ cDefine('S_IFBLK') }}}: + case {{{ cDefine('S_IFIFO') }}}: + case {{{ cDefine('S_IFSOCK') }}}: + break; + default: + ___setErrNo(ERRNO_CODES.EINVAL); + return -1; } try { FS.mknod(path, mode, dev); @@ -4273,9 +4279,11 @@ LibraryManager.library = { __cxa_guard_release: function() {}, __cxa_guard_abort: function() {}, +#if USE_TYPED_ARRAYS != 2 _ZTVN10__cxxabiv119__pointer_type_infoE: [0], // is a pointer _ZTVN10__cxxabiv117__class_type_infoE: [1], // no inherited classes _ZTVN10__cxxabiv120__si_class_type_infoE: [2], // yes inherited classes +#endif // Exceptions __cxa_allocate_exception: function(size) { @@ -4657,20 +4665,28 @@ LibraryManager.library = { cos: 'Math.cos', cosf: 'Math.cos', + cosl: 'Math.cos', sin: 'Math.sin', sinf: 'Math.sin', + sinl: 'Math.sin', tan: 'Math.tan', tanf: 'Math.tan', + tanl: 'Math.tan', acos: 'Math.acos', acosf: 'Math.acos', + acosl: 'Math.acos', asin: 'Math.asin', asinf: 'Math.asin', + asinl: 'Math.asin', atan: 'Math.atan', atanf: 'Math.atan', + atanl: 'Math.atan', atan2: 'Math.atan2', atan2f: 'Math.atan2', + atan2l: 'Math.atan2', exp: 'Math.exp', expf: 'Math.exp', + expl: 'Math.exp', // The erf and erfc functions are inspired from // http://www.digitalmars.com/archives/cplusplus/3634.html @@ -4706,7 +4722,8 @@ LibraryManager.library = { } while (Math.abs(q1 - q2) / q2 > MATH_TOLERANCE); return (ONE_SQRTPI * Math.exp(- x * x) * q2); }, - erfcf: 'erfcf', + erfcf: 'erfc', + erfcl: 'erfc', erf__deps: ['erfc'], erf: function(x) { var MATH_TOLERANCE = 1E-12; @@ -4730,6 +4747,7 @@ LibraryManager.library = { return (TWO_SQRTPI * sum); }, erff: 'erf', + erfl: 'erf', log: 'Math.log', logf: 'Math.log', logl: 'Math.log', @@ -4818,6 +4836,7 @@ LibraryManager.library = { return __reallyNegative(a) === __reallyNegative(b) ? a : -a; }, copysignf: 'copysign', + copysignl: 'copysign', __signbit__deps: ['copysign'], __signbit: function(x) { // We implement using copysign so that we get support @@ -4830,56 +4849,70 @@ LibraryManager.library = { return Math.sqrt(a*a + b*b); }, hypotf: 'hypot', + hypotl: 'hypot', sinh: function(x) { var p = Math.pow(Math.E, x); return (p - (1 / p)) / 2; }, sinhf: 'sinh', + sinhl: 'sinh', cosh: function(x) { var p = Math.pow(Math.E, x); return (p + (1 / p)) / 2; }, coshf: 'cosh', + coshl: 'cosh', tanh__deps: ['sinh', 'cosh'], tanh: function(x) { return _sinh(x) / _cosh(x); }, tanhf: 'tanh', + tanhl: 'tanh', asinh: function(x) { return Math.log(x + Math.sqrt(x * x + 1)); }, asinhf: 'asinh', + asinhl: 'asinh', acosh: function(x) { return Math.log(x * 1 + Math.sqrt(x * x - 1)); }, acoshf: 'acosh', + acoshl: 'acosh', atanh: function(x) { return Math.log((1 + x) / (1 - x)) / 2; }, atanhf: 'atanh', + atanhl: 'atanh', exp2: function(x) { return Math.pow(2, x); }, exp2f: 'exp2', + exp2l: 'exp2', expm1: function(x) { return Math.exp(x) - 1; }, expm1f: 'expm1', + expm1l: 'expm1', round: function(x) { return (x < 0) ? -Math.round(-x) : Math.round(x); }, roundf: 'round', + roundl: 'round', lround: 'round', lroundf: 'round', + lroundl: 'round', llround: 'round', llroundf: 'round', + llroundl: 'round', rint: function(x) { if (Math.abs(x % 1) !== 0.5) return Math.round(x); return x + x % 2 + ((x < 0) ? 1 : -1); }, rintf: 'rint', + rintl: 'rint', lrint: 'rint', lrintf: 'rint', + lrintl: 'rint', #if USE_TYPED_ARRAYS == 2 llrint: function(x) { x = (x < 0) ? -Math.round(-x) : Math.round(x); @@ -4889,50 +4922,63 @@ LibraryManager.library = { llrint: 'rint', #endif llrintf: 'llrint', + llrintl: 'llrint', nearbyint: 'rint', nearbyintf: 'rint', + nearbyintl: 'rint', trunc: function(x) { return (x < 0) ? Math.ceil(x) : Math.floor(x); }, truncf: 'trunc', + truncl: 'trunc', fdim: function(x, y) { return (x > y) ? x - y : 0; }, fdimf: 'fdim', + fdiml: 'fdim', fmax: function(x, y) { return isNaN(x) ? y : isNaN(y) ? x : Math.max(x, y); }, fmaxf: 'fmax', + fmaxl: 'fmax', fmin: function(x, y) { return isNaN(x) ? y : isNaN(y) ? x : Math.min(x, y); }, fminf: 'fmin', + fminl: 'fmin', fma: function(x, y, z) { return x * y + z; }, fmaf: 'fma', + fmal: 'fma', fmod: function(x, y) { return x % y; }, fmodf: 'fmod', + fmodl: 'fmod', remainder: 'fmod', remainderf: 'fmod', + remainderl: 'fmod', log10: function(x) { return Math.log(x) / Math.LN10; }, log10f: 'log10', + log10l: 'log10', log1p: function(x) { return Math.log(1 + x); }, log1pf: 'log1p', + log1pl: 'log1p', log2: function(x) { return Math.log(x) / Math.LN2; }, log2f: 'log2', + log2l: 'log2', nan: function(x) { return NaN; }, nanf: 'nan', + nanl: 'nan', sincos: function(x, sine, cosine) { var sineVal = Math.sin(x), @@ -4940,6 +4986,7 @@ LibraryManager.library = { {{{ makeSetValue('sine', '0', 'sineVal', 'double') }}}; {{{ makeSetValue('cosine', '0', 'cosineVal', 'double') }}}; }, + sincosl: 'sincos', sincosf: function(x, sine, cosine) { var sineVal = Math.sin(x), @@ -5296,8 +5343,8 @@ LibraryManager.library = { ['i32', 'tm_zone']]), // Statically allocated time struct. __tm_current: 'allocate({{{ Runtime.QUANTUM_SIZE }}}*26, "i8", ALLOC_STATIC)', - // Statically allocated timezone strings. - __tm_timezones: {}, + // Statically allocated timezone string. We only use GMT as a timezone. + __tm_timezone: 'allocate(intArrayFromString("GMT"), "i8", ALLOC_STATIC)', // Statically allocated time strings. __tm_formatted: 'allocate({{{ Runtime.QUANTUM_SIZE }}}*26, "i8", ALLOC_STATIC)', @@ -5325,7 +5372,7 @@ LibraryManager.library = { return _gmtime_r(time, ___tm_current); }, - gmtime_r__deps: ['__tm_struct_layout', '__tm_timezones'], + gmtime_r__deps: ['__tm_struct_layout', '__tm_timezone'], gmtime_r: function(time, tmPtr) { var date = new Date({{{ makeGetValue('time', 0, 'i32') }}}*1000); var offsets = ___tm_struct_layout; @@ -5347,12 +5394,7 @@ LibraryManager.library = { start.setUTCMilliseconds(0); var yday = Math.floor((date.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)); {{{ makeSetValue('tmPtr', 'offsets.tm_yday', 'yday', 'i32') }}} - - var timezone = "GMT"; - if (!(timezone in ___tm_timezones)) { - ___tm_timezones[timezone] = allocate(intArrayFromString(timezone), 'i8', ALLOC_NORMAL); - } - {{{ makeSetValue('tmPtr', 'offsets.tm_zone', '___tm_timezones[timezone]', 'i32') }}} + {{{ makeSetValue('tmPtr', 'offsets.tm_zone', '___tm_timezone', 'i32') }}} return tmPtr; }, @@ -5371,7 +5413,7 @@ LibraryManager.library = { return _localtime_r(time, ___tm_current); }, - localtime_r__deps: ['__tm_struct_layout', '__tm_timezones', 'tzset'], + localtime_r__deps: ['__tm_struct_layout', '__tm_timezone', 'tzset'], localtime_r: function(time, tmPtr) { _tzset(); var offsets = ___tm_struct_layout; @@ -5392,11 +5434,7 @@ LibraryManager.library = { var dst = Number(start.getTimezoneOffset() != date.getTimezoneOffset()); {{{ makeSetValue('tmPtr', 'offsets.tm_isdst', 'dst', 'i32') }}} - var timezone = 'GMT'; // XXX do not rely on browser timezone info, it is very unpredictable | date.toString().match(/\(([A-Z]+)\)/)[1]; - if (!(timezone in ___tm_timezones)) { - ___tm_timezones[timezone] = allocate(intArrayFromString(timezone), 'i8', ALLOC_NORMAL); - } - {{{ makeSetValue('tmPtr', 'offsets.tm_zone', '___tm_timezones[timezone]', 'i32') }}} + {{{ makeSetValue('tmPtr', 'offsets.tm_zone', '___tm_timezone', 'i32') }}} return tmPtr; }, @@ -6078,7 +6116,7 @@ LibraryManager.library = { // int clock_gettime(clockid_t clk_id, struct timespec *tp); var now = Date.now(); {{{ makeSetValue('tp', '___timespec_struct_layout.tv_sec', 'Math.floor(now/1000)', 'i32') }}}; // seconds - {{{ makeSetValue('tp', '___timespec_struct_layout.tv_nsec', '0', 'i32') }}}; // nanoseconds - not supported + {{{ makeSetValue('tp', '___timespec_struct_layout.tv_nsec', '(now % 1000) * 1000 * 1000', 'i32') }}}; // nanoseconds (really milliseconds) return 0; }, clock_settime: function(clk_id, tp) { @@ -6090,7 +6128,7 @@ LibraryManager.library = { clock_getres: function(clk_id, res) { // int clock_getres(clockid_t clk_id, struct timespec *res); {{{ makeSetValue('res', '___timespec_struct_layout.tv_sec', '1', 'i32') }}} - {{{ makeSetValue('res', '___timespec_struct_layout.tv_nsec', '0', 'i32') }}} + {{{ makeSetValue('res', '___timespec_struct_layout.tv_nsec', '1000 * 1000', 'i32') }}} // resolution is milliseconds return 0; }, @@ -7399,7 +7437,7 @@ LibraryManager.library = { var aliasesBuf = _malloc(4); {{{ makeSetValue('aliasesBuf', '0', '0', 'i8*') }}} {{{ makeSetValue('ret', '___hostent_struct_layout.h_aliases', 'aliasesBuf', 'i8**') }}} - var afinet = {{{ cDefine("AF_INET") }}}; + var afinet = {{{ cDefine('AF_INET') }}}; {{{ makeSetValue('ret', '___hostent_struct_layout.h_addrtype', 'afinet', 'i32') }}} {{{ makeSetValue('ret', '___hostent_struct_layout.h_length', '4', 'i32') }}} var addrListBuf = _malloc(12); diff --git a/src/library_fs.js b/src/library_fs.js index 5573dc27..4a150d80 100644 --- a/src/library_fs.js +++ b/src/library_fs.js @@ -14,11 +14,10 @@ mergeInto(LibraryManager.library, { 'Module["FS_createDevice"] = FS.createDevice;', $FS: { root: null, - nodes: [null], devices: [null], streams: [null], nextInode: 1, - name_table: null, + nameTable: null, currentPath: '/', initialized: false, // Whether we are currently ignoring permissions. Useful when preparing the @@ -49,6 +48,76 @@ mergeInto(LibraryManager.library, { }, // + // paths + // + cwd: function() { + return FS.currentPath; + }, + lookupPath: function(path, opts) { + path = PATH.resolve(FS.currentPath, path); + opts = opts || { recurse_count: 0 }; + + if (opts.recurse_count > 8) { // max recursive lookup of 8 + throw new FS.ErrnoError(ERRNO_CODES.ELOOP); + } + + // split the path + var parts = PATH.normalizeArray(path.split('/').filter(function(p) { + return !!p; + }), false); + + // start at the root + var current = FS.root; + var current_path = '/'; + + for (var i = 0; i < parts.length; i++) { + var islast = (i === parts.length-1); + if (islast && opts.parent) { + // stop resolving + break; + } + + current = FS.lookupNode(current, parts[i]); + current_path = PATH.join(current_path, parts[i]); + + // jump to the mount's root node if this is a mountpoint + if (FS.isMountpoint(current)) { + current = current.mount.root; + } + + // follow symlinks + // by default, lookupPath will not follow a symlink if it is the final path component. + // setting opts.follow = true will override this behavior. + if (!islast || opts.follow) { + var count = 0; + while (FS.isLink(current.mode)) { + var link = FS.readlink(current_path); + current_path = PATH.resolve(PATH.dirname(current_path), link); + + var lookup = FS.lookupPath(current_path, { recurse_count: opts.recurse_count }); + current = lookup.node; + + if (count++ > 40) { // limit max consecutive symlinks to 40 (SYMLOOP_MAX). + throw new FS.ErrnoError(ERRNO_CODES.ELOOP); + } + } + } + } + + return { path: current_path, node: current }; + }, + getPath: function(node) { + var path; + while (true) { + if (FS.isRoot(node)) { + return path ? PATH.join(node.mount.mountpoint, path) : node.mount.mountpoint; + } + path = path ? PATH.join(node.name, path) : node.name; + node = node.parent; + } + }, + + // // nodes // hashName: function(parentid, name) { @@ -56,19 +125,19 @@ mergeInto(LibraryManager.library, { for (var i = 0; i < name.length; i++) { hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0; } - return ((parentid + hash) >>> 0) % FS.name_table.length; + return ((parentid + hash) >>> 0) % FS.nameTable.length; }, hashAddNode: function(node) { var hash = FS.hashName(node.parent.id, node.name); - node.name_next = FS.name_table[hash]; - FS.name_table[hash] = node; + node.name_next = FS.nameTable[hash]; + FS.nameTable[hash] = node; }, hashRemoveNode: function(node) { var hash = FS.hashName(node.parent.id, node.name); - if (FS.name_table[hash] === node) { - FS.name_table[hash] = node.name_next; + if (FS.nameTable[hash] === node) { + FS.nameTable[hash] = node.name_next; } else { - var current = FS.name_table[hash]; + var current = FS.nameTable[hash]; while (current) { if (current.name_next === node) { current.name_next = node.name_next; @@ -84,7 +153,7 @@ mergeInto(LibraryManager.library, { throw new FS.ErrnoError(err); } var hash = FS.hashName(parent.id, name); - for (var node = FS.name_table[hash]; node; node = node.name_next) { + for (var node = FS.nameTable[hash]; node; node = node.name_next) { if (node.parent.id === parent.id && node.name === name) { return node; } @@ -164,76 +233,6 @@ mergeInto(LibraryManager.library, { }, // - // paths - // - cwd: function() { - return FS.currentPath; - }, - lookupPath: function(path, opts) { - path = PATH.resolve(FS.currentPath, path); - opts = opts || { recurse_count: 0 }; - - if (opts.recurse_count > 8) { // max recursive lookup of 8 - throw new FS.ErrnoError(ERRNO_CODES.ELOOP); - } - - // split the path - var parts = PATH.normalizeArray(path.split('/').filter(function(p) { - return !!p; - }), false); - - // start at the root - var current = FS.root; - var current_path = '/'; - - for (var i = 0; i < parts.length; i++) { - var islast = (i === parts.length-1); - if (islast && opts.parent) { - // stop resolving - break; - } - - current = FS.lookupNode(current, parts[i]); - current_path = PATH.join(current_path, parts[i]); - - // jump to the mount's root node if this is a mountpoint - if (FS.isMountpoint(current)) { - current = current.mount.root; - } - - // follow symlinks - // by default, lookupPath will not follow a symlink if it is the final path component. - // setting opts.follow = true will override this behavior. - if (!islast || opts.follow) { - var count = 0; - while (FS.isLink(current.mode)) { - var link = FS.readlink(current_path); - current_path = PATH.resolve(PATH.dirname(current_path), link); - - var lookup = FS.lookupPath(current_path, { recurse_count: opts.recurse_count }); - current = lookup.node; - - if (count++ > 40) { // limit max consecutive symlinks to 40 (SYMLOOP_MAX). - throw new FS.ErrnoError(ERRNO_CODES.ELOOP); - } - } - } - } - - return { path: current_path, node: current }; - }, - getPath: function(node) { - var path; - while (true) { - if (FS.isRoot(node)) { - return path ? PATH.join(node.mount.mountpoint, path) : node.mount.mountpoint; - } - path = path ? PATH.join(node.name, path) : node.name; - node = node.parent; - } - }, - - // // permissions // flagModes: { @@ -287,18 +286,6 @@ mergeInto(LibraryManager.library, { mayLookup: function(dir) { return FS.nodePermissions(dir, 'x'); }, - mayMknod: function(mode) { - switch (mode & {{{ cDefine('S_IFMT') }}}) { - case {{{ cDefine('S_IFREG') }}}: - case {{{ cDefine('S_IFCHR') }}}: - case {{{ cDefine('S_IFBLK') }}}: - case {{{ cDefine('S_IFIFO') }}}: - case {{{ cDefine('S_IFSOCK') }}}: - return 0; - default: - return ERRNO_CODES.EINVAL; - } - }, mayCreate: function(dir, name) { try { var node = FS.lookupNode(dir, name); @@ -348,45 +335,6 @@ mergeInto(LibraryManager.library, { }, // - // devices - // - // each character device consists of a device id + stream operations. - // when a character device node is created (e.g. /dev/stdin) it is - // assigned a device id that lets us map back to the actual device. - // by default, each character device stream (e.g. _stdin) uses chrdev_stream_ops. - // however, once opened, the stream's operations are overridden with - // the operations of the device its underlying node maps back to. - chrdev_stream_ops: { - open: function(stream) { - var device = FS.getDevice(stream.node.rdev); - // override node's stream ops with the device's - stream.stream_ops = device.stream_ops; - // forward the open call - if (stream.stream_ops.open) { - stream.stream_ops.open(stream); - } - }, - llseek: function() { - throw new FS.ErrnoError(ERRNO_CODES.ESPIPE); - } - }, - major: function(dev) { - return ((dev) >> 8); - }, - minor: function(dev) { - return ((dev) & 0xff); - }, - makedev: function(ma, mi) { - return ((ma) << 8 | (mi)); - }, - registerDevice: function(dev, ops) { - FS.devices[dev] = { stream_ops: ops }; - }, - getDevice: function(dev) { - return FS.devices[dev]; - }, - - // // streams // MAX_OPEN_FDS: 4096, @@ -433,563 +381,46 @@ mergeInto(LibraryManager.library, { }, // - // compatibility + // devices // - getMode: function(canRead, canWrite) { - var mode = 0; - if (canRead) mode |= {{{ cDefine('S_IRUGO') }}} | {{{ cDefine('S_IXUGO') }}}; - if (canWrite) mode |= {{{ cDefine('S_IWUGO') }}}; - return mode; - }, - joinPath: function(parts, forceRelative) { - var path = PATH.join.apply(null, parts); - if (forceRelative && path[0] == '/') path = path.substr(1); - return path; - }, - absolutePath: function(relative, base) { - return PATH.resolve(base, relative); - }, - standardizePath: function(path) { - return PATH.normalize(path); - }, - findObject: function(path, dontResolveLastLink) { - var ret = FS.analyzePath(path, dontResolveLastLink); - if (ret.exists) { - return ret.object; - } else { - ___setErrNo(ret.error); - return null; - } - }, - analyzePath: function(path, dontResolveLastLink) { - // operate from within the context of the symlink's target - try { - var lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); - path = lookup.path; - } catch (e) { - } - var ret = { - isRoot: false, exists: false, error: 0, name: null, path: null, object: null, - parentExists: false, parentPath: null, parentObject: null - }; - try { - var lookup = FS.lookupPath(path, { parent: true }); - ret.parentExists = true; - ret.parentPath = lookup.path; - ret.parentObject = lookup.node; - ret.name = PATH.basename(path); - lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); - ret.exists = true; - ret.path = lookup.path; - ret.object = lookup.node; - ret.name = lookup.node.name; - ret.isRoot = lookup.path === '/'; - } catch (e) { - ret.error = e.errno; - }; - return ret; - }, - createFolder: function(parent, name, canRead, canWrite) { - var path = PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name); - var mode = FS.getMode(canRead, canWrite); - return FS.mkdir(path, mode); - }, - createPath: function(parent, path, canRead, canWrite) { - parent = typeof parent === 'string' ? parent : FS.getPath(parent); - var parts = path.split('/').reverse(); - while (parts.length) { - var part = parts.pop(); - if (!part) continue; - var current = PATH.join(parent, part); - try { - FS.mkdir(current, 0777); - } catch (e) { - // ignore EEXIST - } - parent = current; - } - return current; - }, - createFile: function(parent, name, properties, canRead, canWrite) { - var path = PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name); - var mode = FS.getMode(canRead, canWrite); - return FS.create(path, mode); - }, - createDataFile: function(parent, name, data, canRead, canWrite, canOwn) { - var path = name ? PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name) : parent; - var mode = FS.getMode(canRead, canWrite); - var node = FS.create(path, mode); - if (data) { - if (typeof data === 'string') { - var arr = new Array(data.length); - for (var i = 0, len = data.length; i < len; ++i) arr[i] = data.charCodeAt(i); - data = arr; - } - // make sure we can write to the file - FS.chmod(path, mode | {{{ cDefine('S_IWUGO') }}}); - var stream = FS.open(path, 'w'); - FS.write(stream, data, 0, data.length, 0, canOwn); - FS.close(stream); - FS.chmod(path, mode); - } - return node; - }, - createDevice: function(parent, name, input, output) { - var path = PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name); - var mode = FS.getMode(!!input, !!output); - if (!FS.createDevice.major) FS.createDevice.major = 64; - var dev = FS.makedev(FS.createDevice.major++, 0); - // Create a fake device that a set of stream ops to emulate - // the old behavior. - FS.registerDevice(dev, { - open: function(stream) { - stream.seekable = false; - }, - close: function(stream) { - // flush any pending line data - if (output && output.buffer && output.buffer.length) { - output({{{ charCode('\n') }}}); - } - }, - read: function(stream, buffer, offset, length, pos /* ignored */) { - var bytesRead = 0; - for (var i = 0; i < length; i++) { - var result; - try { - result = input(); - } catch (e) { - throw new FS.ErrnoError(ERRNO_CODES.EIO); - } - if (result === undefined && bytesRead === 0) { - throw new FS.ErrnoError(ERRNO_CODES.EAGAIN); - } - if (result === null || result === undefined) break; - bytesRead++; - buffer[offset+i] = result; - } - if (bytesRead) { - stream.node.timestamp = Date.now(); - } - return bytesRead; - }, - write: function(stream, buffer, offset, length, pos) { - for (var i = 0; i < length; i++) { - try { - output(buffer[offset+i]); - } catch (e) { - throw new FS.ErrnoError(ERRNO_CODES.EIO); - } - } - if (length) { - stream.node.timestamp = Date.now(); - } - return i; - } - }); - return FS.mkdev(path, mode, dev); - }, - createLink: function(parent, name, target, canRead, canWrite) { - var path = PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name); - return FS.symlink(target, path); - }, - // Makes sure a file's contents are loaded. Returns whether the file has - // been loaded successfully. No-op for files that have been loaded already. - forceLoadFile: function(obj) { - if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true; - var success = true; - if (typeof XMLHttpRequest !== 'undefined') { - throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread."); - } else if (Module['read']) { - // Command-line. - try { - // WARNING: Can't read binary files in V8's d8 or tracemonkey's js, as - // read() will try to parse UTF8. - obj.contents = intArrayFromString(Module['read'](obj.url), true); - } catch (e) { - success = false; - } - } else { - throw new Error('Cannot load without read() or XMLHttpRequest.'); - } - if (!success) ___setErrNo(ERRNO_CODES.EIO); - return success; - }, - // Creates a file record for lazy-loading from a URL. XXX This requires a synchronous - // XHR, which is not possible in browsers except in a web worker! Use preloading, - // either --preload-file in emcc or FS.createPreloadedFile - createLazyFile: function(parent, name, url, canRead, canWrite) { - if (typeof XMLHttpRequest !== 'undefined') { - if (!ENVIRONMENT_IS_WORKER) throw 'Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc'; - // Lazy chunked Uint8Array (implements get and length from Uint8Array). Actual getting is abstracted away for eventual reuse. - var LazyUint8Array = function() { - this.lengthKnown = false; - this.chunks = []; // Loaded chunks. Index is the chunk number - } - LazyUint8Array.prototype.get = function(idx) { - if (idx > this.length-1 || idx < 0) { - return undefined; - } - var chunkOffset = idx % this.chunkSize; - var chunkNum = Math.floor(idx / this.chunkSize); - return this.getter(chunkNum)[chunkOffset]; - } - LazyUint8Array.prototype.setDataGetter = function(getter) { - this.getter = getter; - } - LazyUint8Array.prototype.cacheLength = function() { - // Find length - var xhr = new XMLHttpRequest(); - xhr.open('HEAD', url, false); - xhr.send(null); - if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); - var datalength = Number(xhr.getResponseHeader("Content-length")); - var header; - var hasByteServing = (header = xhr.getResponseHeader("Accept-Ranges")) && header === "bytes"; -#if SMALL_XHR_CHUNKS - var chunkSize = 1024; // Chunk size in bytes -#else - var chunkSize = 1024*1024; // Chunk size in bytes -#endif - - if (!hasByteServing) chunkSize = datalength; - - // Function to get a range from the remote URL. - var doXHR = (function(from, to) { - if (from > to) throw new Error("invalid range (" + from + ", " + to + ") or no bytes requested!"); - if (to > datalength-1) throw new Error("only " + datalength + " bytes available! programmer error!"); - - // TODO: Use mozResponseArrayBuffer, responseStream, etc. if available. - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, false); - if (datalength !== chunkSize) xhr.setRequestHeader("Range", "bytes=" + from + "-" + to); - - // Some hints to the browser that we want binary data. - if (typeof Uint8Array != 'undefined') xhr.responseType = 'arraybuffer'; - if (xhr.overrideMimeType) { - xhr.overrideMimeType('text/plain; charset=x-user-defined'); - } - - xhr.send(null); - if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); - if (xhr.response !== undefined) { - return new Uint8Array(xhr.response || []); - } else { - return intArrayFromString(xhr.responseText || '', true); - } - }); - var lazyArray = this; - lazyArray.setDataGetter(function(chunkNum) { - var start = chunkNum * chunkSize; - var end = (chunkNum+1) * chunkSize - 1; // including this byte - end = Math.min(end, datalength-1); // if datalength-1 is selected, this is the last block - if (typeof(lazyArray.chunks[chunkNum]) === "undefined") { - lazyArray.chunks[chunkNum] = doXHR(start, end); - } - if (typeof(lazyArray.chunks[chunkNum]) === "undefined") throw new Error("doXHR failed!"); - return lazyArray.chunks[chunkNum]; - }); - - this._length = datalength; - this._chunkSize = chunkSize; - this.lengthKnown = true; - } - - var lazyArray = new LazyUint8Array(); - Object.defineProperty(lazyArray, "length", { - get: function() { - if(!this.lengthKnown) { - this.cacheLength(); - } - return this._length; - } - }); - Object.defineProperty(lazyArray, "chunkSize", { - get: function() { - if(!this.lengthKnown) { - this.cacheLength(); - } - return this._chunkSize; - } - }); - - var properties = { isDevice: false, contents: lazyArray }; - } else { - var properties = { isDevice: false, url: url }; - } - - var node = FS.createFile(parent, name, properties, canRead, canWrite); - // This is a total hack, but I want to get this lazy file code out of the - // core of MEMFS. If we want to keep this lazy file concept I feel it should - // be its own thin LAZYFS proxying calls to MEMFS. - if (properties.contents) { - node.contents = properties.contents; - } else if (properties.url) { - node.contents = null; - node.url = properties.url; - } - // override each stream op with one that tries to force load the lazy file first - var stream_ops = {}; - var keys = Object.keys(node.stream_ops); - keys.forEach(function(key) { - var fn = node.stream_ops[key]; - stream_ops[key] = function() { - if (!FS.forceLoadFile(node)) { - throw new FS.ErrnoError(ERRNO_CODES.EIO); - } - return fn.apply(null, arguments); - }; - }); - // use a custom read function - stream_ops.read = function(stream, buffer, offset, length, position) { - if (!FS.forceLoadFile(node)) { - throw new FS.ErrnoError(ERRNO_CODES.EIO); - } - var contents = stream.node.contents; - var size = Math.min(contents.length - position, length); - if (contents.slice) { // normal array - for (var i = 0; i < size; i++) { - buffer[offset + i] = contents[position + i]; - } - } else { - for (var i = 0; i < size; i++) { // LazyUint8Array from sync binary XHR - buffer[offset + i] = contents.get(position + i); - } - } - return size; - }; - node.stream_ops = stream_ops; - return node; - }, - // Preloads a file asynchronously. You can call this before run, for example in - // preRun. run will be delayed until this file arrives and is set up. - // If you call it after run(), you may want to pause the main loop until it - // completes, if so, you can use the onload parameter to be notified when - // that happens. - // In addition to normally creating the file, we also asynchronously preload - // the browser-friendly versions of it: For an image, we preload an Image - // element and for an audio, and Audio. These are necessary for SDL_Image - // and _Mixer to find the files in preloadedImages/Audios. - // You can also call this with a typed array instead of a url. It will then - // do preloading for the Image/Audio part, as if the typed array were the - // result of an XHR that you did manually. - createPreloadedFile: function(parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile, canOwn) { - Browser.init(); - // TODO we should allow people to just pass in a complete filename instead - // of parent and name being that we just join them anyways - var fullname = name ? PATH.resolve(PATH.join(parent, name)) : parent; - function processData(byteArray) { - function finish(byteArray) { - if (!dontCreateFile) { - FS.createDataFile(parent, name, byteArray, canRead, canWrite, canOwn); - } - if (onload) onload(); - removeRunDependency('cp ' + fullname); + // each character device consists of a device id + stream operations. + // when a character device node is created (e.g. /dev/stdin) it is + // assigned a device id that lets us map back to the actual device. + // by default, each character device stream (e.g. _stdin) uses chrdev_stream_ops. + // however, once opened, the stream's operations are overridden with + // the operations of the device its underlying node maps back to. + chrdev_stream_ops: { + open: function(stream) { + var device = FS.getDevice(stream.node.rdev); + // override node's stream ops with the device's + stream.stream_ops = device.stream_ops; + // forward the open call + if (stream.stream_ops.open) { + stream.stream_ops.open(stream); } - var handled = false; - Module['preloadPlugins'].forEach(function(plugin) { - if (handled) return; - if (plugin['canHandle'](fullname)) { - plugin['handle'](byteArray, fullname, finish, function() { - if (onerror) onerror(); - removeRunDependency('cp ' + fullname); - }); - handled = true; - } - }); - if (!handled) finish(byteArray); - } - addRunDependency('cp ' + fullname); - if (typeof url == 'string') { - Browser.asyncLoad(url, function(byteArray) { - processData(byteArray); - }, onerror); - } else { - processData(url); - } - }, - - indexedDB: function() { - return window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; - }, - - DB_NAME: function() { - return 'EM_FS_' + window.location.pathname; - }, - DB_VERSION: 20, - DB_STORE_NAME: 'FILE_DATA', - - // asynchronously saves a list of files to an IndexedDB. The DB will be created if not already existing. - saveFilesToDB: function(paths, onload, onerror) { - onload = onload || function(){}; - onerror = onerror || function(){}; - var indexedDB = FS.indexedDB(); - try { - var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION); - } catch (e) { - return onerror(e); + }, + llseek: function() { + throw new FS.ErrnoError(ERRNO_CODES.ESPIPE); } - openRequest.onupgradeneeded = function() { - console.log('creating db'); - var db = openRequest.result; - db.createObjectStore(FS.DB_STORE_NAME); - }; - openRequest.onsuccess = function() { - var db = openRequest.result; - var transaction = db.transaction([FS.DB_STORE_NAME], 'readwrite'); - var files = transaction.objectStore(FS.DB_STORE_NAME); - var ok = 0, fail = 0, total = paths.length; - function finish() { - if (fail == 0) onload(); else onerror(); - } - paths.forEach(function(path) { - var putRequest = files.put(FS.analyzePath(path).object.contents, path); - putRequest.onsuccess = function() { ok++; if (ok + fail == total) finish() }; - putRequest.onerror = function() { fail++; if (ok + fail == total) finish() }; - }); - transaction.onerror = onerror; - }; - openRequest.onerror = onerror; }, - - // asychronously loads a file from IndexedDB. - loadFilesFromDB: function(paths, onload, onerror) { - onload = onload || function(){}; - onerror = onerror || function(){}; - var indexedDB = FS.indexedDB(); - try { - var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION); - } catch (e) { - return onerror(e); - } - openRequest.onupgradeneeded = onerror; // no database to load from - openRequest.onsuccess = function() { - var db = openRequest.result; - try { - var transaction = db.transaction([FS.DB_STORE_NAME], 'readonly'); - } catch(e) { - onerror(e); - return; - } - var files = transaction.objectStore(FS.DB_STORE_NAME); - var ok = 0, fail = 0, total = paths.length; - function finish() { - if (fail == 0) onload(); else onerror(); - } - paths.forEach(function(path) { - var getRequest = files.get(path); - getRequest.onsuccess = function() { - if (FS.analyzePath(path).exists) { - FS.unlink(path); - } - FS.createDataFile(PATH.dirname(path), PATH.basename(path), getRequest.result, true, true, true); - ok++; - if (ok + fail == total) finish(); - }; - getRequest.onerror = function() { fail++; if (ok + fail == total) finish() }; - }); - transaction.onerror = onerror; - }; - openRequest.onerror = onerror; - }, - - // - // general - // - createDefaultDirectories: function() { - FS.mkdir('/tmp', 0777); - }, - createDefaultDevices: function() { - // create /dev - FS.mkdir('/dev', 0777); - // setup /dev/null - FS.registerDevice(FS.makedev(1, 3), { - read: function() { return 0; }, - write: function() { return 0; } - }); - FS.mkdev('/dev/null', 0666, FS.makedev(1, 3)); - // setup /dev/tty and /dev/tty1 - // stderr needs to print output using Module['printErr'] - // so we register a second tty just for it. - TTY.register(FS.makedev(5, 0), TTY.default_tty_ops); - TTY.register(FS.makedev(6, 0), TTY.default_tty1_ops); - FS.mkdev('/dev/tty', 0666, FS.makedev(5, 0)); - FS.mkdev('/dev/tty1', 0666, FS.makedev(6, 0)); - // we're not going to emulate the actual shm device, - // just create the tmp dirs that reside in it commonly - FS.mkdir('/dev/shm', 0777); - FS.mkdir('/dev/shm/tmp', 0777); + major: function(dev) { + return ((dev) >> 8); }, - createStandardStreams: function() { - // TODO deprecate the old functionality of a single - // input / output callback and that utilizes FS.createDevice - // and instead require a unique set of stream ops - - // by default, we symlink the standard streams to the - // default tty devices. however, if the standard streams - // have been overwritten we create a unique device for - // them instead. - if (Module['stdin']) { - FS.createDevice('/dev', 'stdin', Module['stdin']); - } else { - FS.symlink('/dev/tty', '/dev/stdin'); - } - if (Module['stdout']) { - FS.createDevice('/dev', 'stdout', null, Module['stdout']); - } else { - FS.symlink('/dev/tty', '/dev/stdout'); - } - if (Module['stderr']) { - FS.createDevice('/dev', 'stderr', null, Module['stderr']); - } else { - FS.symlink('/dev/tty1', '/dev/stderr'); - } - - // open default streams for the stdin, stdout and stderr devices - var stdin = FS.open('/dev/stdin', 'r'); - {{{ makeSetValue(makeGlobalUse('_stdin'), 0, 'stdin.fd', 'void*') }}}; - assert(stdin.fd === 1, 'invalid handle for stdin (' + stdin.fd + ')'); - - var stdout = FS.open('/dev/stdout', 'w'); - {{{ makeSetValue(makeGlobalUse('_stdout'), 0, 'stdout.fd', 'void*') }}}; - assert(stdout.fd === 2, 'invalid handle for stdout (' + stdout.fd + ')'); - - var stderr = FS.open('/dev/stderr', 'w'); - {{{ makeSetValue(makeGlobalUse('_stderr'), 0, 'stderr.fd', 'void*') }}}; - assert(stderr.fd === 3, 'invalid handle for stderr (' + stderr.fd + ')'); + minor: function(dev) { + return ((dev) & 0xff); }, - staticInit: function() { - FS.name_table = new Array(4096); - - FS.root = FS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 0777, 0); - FS.mount(MEMFS, {}, '/'); - - FS.createDefaultDirectories(); - FS.createDefaultDevices(); + makedev: function(ma, mi) { + return ((ma) << 8 | (mi)); }, - init: function(input, output, error) { - assert(!FS.init.initialized, 'FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)'); - FS.init.initialized = true; - - // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here - Module['stdin'] = input || Module['stdin']; - Module['stdout'] = output || Module['stdout']; - Module['stderr'] = error || Module['stderr']; - - FS.createStandardStreams(); + registerDevice: function(dev, ops) { + FS.devices[dev] = { stream_ops: ops }; }, - quit: function() { - FS.init.initialized = false; - for (var i = 0; i < FS.streams.length; i++) { - var stream = FS.streams[i]; - if (!stream) { - continue; - } - FS.close(stream); - } + getDevice: function(dev) { + return FS.devices[dev]; }, // - // vfs functionality + // core // mount: function(type, opts, mountpoint) { var mount = { @@ -1036,16 +467,22 @@ mergeInto(LibraryManager.library, { }, // helpers to create specific types of nodes create: function(path, mode) { + mode = mode !== undefined ? mode : 0666; mode &= {{{ cDefine('S_IALLUGO') }}}; mode |= {{{ cDefine('S_IFREG') }}}; return FS.mknod(path, mode, 0); }, mkdir: function(path, mode) { + mode = mode !== undefined ? mode : 0777; mode &= {{{ cDefine('S_IRWXUGO') }}} | {{{ cDefine('S_ISVTX') }}}; mode |= {{{ cDefine('S_IFDIR') }}}; return FS.mknod(path, mode, 0); }, mkdev: function(path, mode, dev) { + if (typeof(dev) === 'undefined') { + dev = mode; + mode = 0666; + } mode |= {{{ cDefine('S_IFCHR') }}}; return FS.mknod(path, mode, dev); }, @@ -1471,6 +908,606 @@ mergeInto(LibraryManager.library, { throw new FS.ErrnoError(ERRNO_CODES.ENOTTY); } return stream.stream_ops.ioctl(stream, cmd, arg); + }, + readFile: function(path, opts) { + opts = opts || {}; + opts.flags = opts.flags || 'r'; + opts.encoding = opts.encoding || 'binary'; + var ret; + var stream = FS.open(path, opts.flags); + var stat = FS.stat(path); + var length = stat.size; + var buf = new Uint8Array(length); + FS.read(stream, buf, 0, length, 0); + if (opts.encoding === 'utf8') { + ret = ''; + var utf8 = new Runtime.UTF8Processor(); + for (var i = 0; i < length; i++) { + ret += utf8.processCChar(buf[i]); + } + } else if (opts.encoding === 'binary') { + ret = buf; + } else { + throw new Error('Invalid encoding type "' + opts.encoding + '"'); + } + FS.close(stream); + return ret; + }, + writeFile: function(path, data, opts) { + opts = opts || {}; + opts.flags = opts.flags || 'w'; + opts.encoding = opts.encoding || 'utf8'; + var stream = FS.open(path, opts.flags, opts.mode); + if (opts.encoding === 'utf8') { + var utf8 = new Runtime.UTF8Processor(); + var buf = new Uint8Array(utf8.processJSString(data)); + FS.write(stream, buf, 0, buf.length, 0); + } else if (opts.encoding === 'binary') { + FS.write(stream, data, 0, data.length, 0); + } else { + throw new Error('Invalid encoding type "' + opts.encoding + '"'); + } + FS.close(stream); + }, + + // + // module-level FS code + // TODO move to pre/postamble + // + createDefaultDirectories: function() { + FS.mkdir('/tmp'); + }, + createDefaultDevices: function() { + // create /dev + FS.mkdir('/dev'); + // setup /dev/null + FS.registerDevice(FS.makedev(1, 3), { + read: function() { return 0; }, + write: function() { return 0; } + }); + FS.mkdev('/dev/null', FS.makedev(1, 3)); + // setup /dev/tty and /dev/tty1 + // stderr needs to print output using Module['printErr'] + // so we register a second tty just for it. + TTY.register(FS.makedev(5, 0), TTY.default_tty_ops); + TTY.register(FS.makedev(6, 0), TTY.default_tty1_ops); + FS.mkdev('/dev/tty', FS.makedev(5, 0)); + FS.mkdev('/dev/tty1', FS.makedev(6, 0)); + // we're not going to emulate the actual shm device, + // just create the tmp dirs that reside in it commonly + FS.mkdir('/dev/shm'); + FS.mkdir('/dev/shm/tmp'); + }, + createStandardStreams: function() { + // TODO deprecate the old functionality of a single + // input / output callback and that utilizes FS.createDevice + // and instead require a unique set of stream ops + + // by default, we symlink the standard streams to the + // default tty devices. however, if the standard streams + // have been overwritten we create a unique device for + // them instead. + if (Module['stdin']) { + FS.createDevice('/dev', 'stdin', Module['stdin']); + } else { + FS.symlink('/dev/tty', '/dev/stdin'); + } + if (Module['stdout']) { + FS.createDevice('/dev', 'stdout', null, Module['stdout']); + } else { + FS.symlink('/dev/tty', '/dev/stdout'); + } + if (Module['stderr']) { + FS.createDevice('/dev', 'stderr', null, Module['stderr']); + } else { + FS.symlink('/dev/tty1', '/dev/stderr'); + } + + // open default streams for the stdin, stdout and stderr devices + var stdin = FS.open('/dev/stdin', 'r'); + {{{ makeSetValue(makeGlobalUse('_stdin'), 0, 'stdin.fd', 'void*') }}}; + assert(stdin.fd === 1, 'invalid handle for stdin (' + stdin.fd + ')'); + + var stdout = FS.open('/dev/stdout', 'w'); + {{{ makeSetValue(makeGlobalUse('_stdout'), 0, 'stdout.fd', 'void*') }}}; + assert(stdout.fd === 2, 'invalid handle for stdout (' + stdout.fd + ')'); + + var stderr = FS.open('/dev/stderr', 'w'); + {{{ makeSetValue(makeGlobalUse('_stderr'), 0, 'stderr.fd', 'void*') }}}; + assert(stderr.fd === 3, 'invalid handle for stderr (' + stderr.fd + ')'); + }, + staticInit: function() { + FS.nameTable = new Array(4096); + + FS.root = FS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 0777, 0); + FS.mount(MEMFS, {}, '/'); + + FS.createDefaultDirectories(); + FS.createDefaultDevices(); + }, + init: function(input, output, error) { + assert(!FS.init.initialized, 'FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)'); + FS.init.initialized = true; + + // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here + Module['stdin'] = input || Module['stdin']; + Module['stdout'] = output || Module['stdout']; + Module['stderr'] = error || Module['stderr']; + + FS.createStandardStreams(); + }, + quit: function() { + FS.init.initialized = false; + for (var i = 0; i < FS.streams.length; i++) { + var stream = FS.streams[i]; + if (!stream) { + continue; + } + FS.close(stream); + } + }, + + // + // old v1 compatibility functions + // + getMode: function(canRead, canWrite) { + var mode = 0; + if (canRead) mode |= {{{ cDefine('S_IRUGO') }}} | {{{ cDefine('S_IXUGO') }}}; + if (canWrite) mode |= {{{ cDefine('S_IWUGO') }}}; + return mode; + }, + joinPath: function(parts, forceRelative) { + var path = PATH.join.apply(null, parts); + if (forceRelative && path[0] == '/') path = path.substr(1); + return path; + }, + absolutePath: function(relative, base) { + return PATH.resolve(base, relative); + }, + standardizePath: function(path) { + return PATH.normalize(path); + }, + findObject: function(path, dontResolveLastLink) { + var ret = FS.analyzePath(path, dontResolveLastLink); + if (ret.exists) { + return ret.object; + } else { + ___setErrNo(ret.error); + return null; + } + }, + analyzePath: function(path, dontResolveLastLink) { + // operate from within the context of the symlink's target + try { + var lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); + path = lookup.path; + } catch (e) { + } + var ret = { + isRoot: false, exists: false, error: 0, name: null, path: null, object: null, + parentExists: false, parentPath: null, parentObject: null + }; + try { + var lookup = FS.lookupPath(path, { parent: true }); + ret.parentExists = true; + ret.parentPath = lookup.path; + ret.parentObject = lookup.node; + ret.name = PATH.basename(path); + lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); + ret.exists = true; + ret.path = lookup.path; + ret.object = lookup.node; + ret.name = lookup.node.name; + ret.isRoot = lookup.path === '/'; + } catch (e) { + ret.error = e.errno; + }; + return ret; + }, + createFolder: function(parent, name, canRead, canWrite) { + var path = PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name); + var mode = FS.getMode(canRead, canWrite); + return FS.mkdir(path, mode); + }, + createPath: function(parent, path, canRead, canWrite) { + parent = typeof parent === 'string' ? parent : FS.getPath(parent); + var parts = path.split('/').reverse(); + while (parts.length) { + var part = parts.pop(); + if (!part) continue; + var current = PATH.join(parent, part); + try { + FS.mkdir(current); + } catch (e) { + // ignore EEXIST + } + parent = current; + } + return current; + }, + createFile: function(parent, name, properties, canRead, canWrite) { + var path = PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name); + var mode = FS.getMode(canRead, canWrite); + return FS.create(path, mode); + }, + createDataFile: function(parent, name, data, canRead, canWrite, canOwn) { + var path = name ? PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name) : parent; + var mode = FS.getMode(canRead, canWrite); + var node = FS.create(path, mode); + if (data) { + if (typeof data === 'string') { + var arr = new Array(data.length); + for (var i = 0, len = data.length; i < len; ++i) arr[i] = data.charCodeAt(i); + data = arr; + } + // make sure we can write to the file + FS.chmod(path, mode | {{{ cDefine('S_IWUGO') }}}); + var stream = FS.open(path, 'w'); + FS.write(stream, data, 0, data.length, 0, canOwn); + FS.close(stream); + FS.chmod(path, mode); + } + return node; + }, + createDevice: function(parent, name, input, output) { + var path = PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name); + var mode = FS.getMode(!!input, !!output); + if (!FS.createDevice.major) FS.createDevice.major = 64; + var dev = FS.makedev(FS.createDevice.major++, 0); + // Create a fake device that a set of stream ops to emulate + // the old behavior. + FS.registerDevice(dev, { + open: function(stream) { + stream.seekable = false; + }, + close: function(stream) { + // flush any pending line data + if (output && output.buffer && output.buffer.length) { + output({{{ charCode('\n') }}}); + } + }, + read: function(stream, buffer, offset, length, pos /* ignored */) { + var bytesRead = 0; + for (var i = 0; i < length; i++) { + var result; + try { + result = input(); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + if (result === undefined && bytesRead === 0) { + throw new FS.ErrnoError(ERRNO_CODES.EAGAIN); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[offset+i] = result; + } + if (bytesRead) { + stream.node.timestamp = Date.now(); + } + return bytesRead; + }, + write: function(stream, buffer, offset, length, pos) { + for (var i = 0; i < length; i++) { + try { + output(buffer[offset+i]); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + } + if (length) { + stream.node.timestamp = Date.now(); + } + return i; + } + }); + return FS.mkdev(path, mode, dev); + }, + createLink: function(parent, name, target, canRead, canWrite) { + var path = PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name); + return FS.symlink(target, path); + }, + // Makes sure a file's contents are loaded. Returns whether the file has + // been loaded successfully. No-op for files that have been loaded already. + forceLoadFile: function(obj) { + if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true; + var success = true; + if (typeof XMLHttpRequest !== 'undefined') { + throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread."); + } else if (Module['read']) { + // Command-line. + try { + // WARNING: Can't read binary files in V8's d8 or tracemonkey's js, as + // read() will try to parse UTF8. + obj.contents = intArrayFromString(Module['read'](obj.url), true); + } catch (e) { + success = false; + } + } else { + throw new Error('Cannot load without read() or XMLHttpRequest.'); + } + if (!success) ___setErrNo(ERRNO_CODES.EIO); + return success; + }, + // Creates a file record for lazy-loading from a URL. XXX This requires a synchronous + // XHR, which is not possible in browsers except in a web worker! Use preloading, + // either --preload-file in emcc or FS.createPreloadedFile + createLazyFile: function(parent, name, url, canRead, canWrite) { + if (typeof XMLHttpRequest !== 'undefined') { + if (!ENVIRONMENT_IS_WORKER) throw 'Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc'; + // Lazy chunked Uint8Array (implements get and length from Uint8Array). Actual getting is abstracted away for eventual reuse. + var LazyUint8Array = function() { + this.lengthKnown = false; + this.chunks = []; // Loaded chunks. Index is the chunk number + } + LazyUint8Array.prototype.get = function(idx) { + if (idx > this.length-1 || idx < 0) { + return undefined; + } + var chunkOffset = idx % this.chunkSize; + var chunkNum = Math.floor(idx / this.chunkSize); + return this.getter(chunkNum)[chunkOffset]; + } + LazyUint8Array.prototype.setDataGetter = function(getter) { + this.getter = getter; + } + LazyUint8Array.prototype.cacheLength = function() { + // Find length + var xhr = new XMLHttpRequest(); + xhr.open('HEAD', url, false); + xhr.send(null); + if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); + var datalength = Number(xhr.getResponseHeader("Content-length")); + var header; + var hasByteServing = (header = xhr.getResponseHeader("Accept-Ranges")) && header === "bytes"; +#if SMALL_XHR_CHUNKS + var chunkSize = 1024; // Chunk size in bytes +#else + var chunkSize = 1024*1024; // Chunk size in bytes +#endif + + if (!hasByteServing) chunkSize = datalength; + + // Function to get a range from the remote URL. + var doXHR = (function(from, to) { + if (from > to) throw new Error("invalid range (" + from + ", " + to + ") or no bytes requested!"); + if (to > datalength-1) throw new Error("only " + datalength + " bytes available! programmer error!"); + + // TODO: Use mozResponseArrayBuffer, responseStream, etc. if available. + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, false); + if (datalength !== chunkSize) xhr.setRequestHeader("Range", "bytes=" + from + "-" + to); + + // Some hints to the browser that we want binary data. + if (typeof Uint8Array != 'undefined') xhr.responseType = 'arraybuffer'; + if (xhr.overrideMimeType) { + xhr.overrideMimeType('text/plain; charset=x-user-defined'); + } + + xhr.send(null); + if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); + if (xhr.response !== undefined) { + return new Uint8Array(xhr.response || []); + } else { + return intArrayFromString(xhr.responseText || '', true); + } + }); + var lazyArray = this; + lazyArray.setDataGetter(function(chunkNum) { + var start = chunkNum * chunkSize; + var end = (chunkNum+1) * chunkSize - 1; // including this byte + end = Math.min(end, datalength-1); // if datalength-1 is selected, this is the last block + if (typeof(lazyArray.chunks[chunkNum]) === "undefined") { + lazyArray.chunks[chunkNum] = doXHR(start, end); + } + if (typeof(lazyArray.chunks[chunkNum]) === "undefined") throw new Error("doXHR failed!"); + return lazyArray.chunks[chunkNum]; + }); + + this._length = datalength; + this._chunkSize = chunkSize; + this.lengthKnown = true; + } + + var lazyArray = new LazyUint8Array(); + Object.defineProperty(lazyArray, "length", { + get: function() { + if(!this.lengthKnown) { + this.cacheLength(); + } + return this._length; + } + }); + Object.defineProperty(lazyArray, "chunkSize", { + get: function() { + if(!this.lengthKnown) { + this.cacheLength(); + } + return this._chunkSize; + } + }); + + var properties = { isDevice: false, contents: lazyArray }; + } else { + var properties = { isDevice: false, url: url }; + } + + var node = FS.createFile(parent, name, properties, canRead, canWrite); + // This is a total hack, but I want to get this lazy file code out of the + // core of MEMFS. If we want to keep this lazy file concept I feel it should + // be its own thin LAZYFS proxying calls to MEMFS. + if (properties.contents) { + node.contents = properties.contents; + } else if (properties.url) { + node.contents = null; + node.url = properties.url; + } + // override each stream op with one that tries to force load the lazy file first + var stream_ops = {}; + var keys = Object.keys(node.stream_ops); + keys.forEach(function(key) { + var fn = node.stream_ops[key]; + stream_ops[key] = function() { + if (!FS.forceLoadFile(node)) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + return fn.apply(null, arguments); + }; + }); + // use a custom read function + stream_ops.read = function(stream, buffer, offset, length, position) { + if (!FS.forceLoadFile(node)) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + var contents = stream.node.contents; + var size = Math.min(contents.length - position, length); + if (contents.slice) { // normal array + for (var i = 0; i < size; i++) { + buffer[offset + i] = contents[position + i]; + } + } else { + for (var i = 0; i < size; i++) { // LazyUint8Array from sync binary XHR + buffer[offset + i] = contents.get(position + i); + } + } + return size; + }; + node.stream_ops = stream_ops; + return node; + }, + // Preloads a file asynchronously. You can call this before run, for example in + // preRun. run will be delayed until this file arrives and is set up. + // If you call it after run(), you may want to pause the main loop until it + // completes, if so, you can use the onload parameter to be notified when + // that happens. + // In addition to normally creating the file, we also asynchronously preload + // the browser-friendly versions of it: For an image, we preload an Image + // element and for an audio, and Audio. These are necessary for SDL_Image + // and _Mixer to find the files in preloadedImages/Audios. + // You can also call this with a typed array instead of a url. It will then + // do preloading for the Image/Audio part, as if the typed array were the + // result of an XHR that you did manually. + createPreloadedFile: function(parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile, canOwn) { + Browser.init(); + // TODO we should allow people to just pass in a complete filename instead + // of parent and name being that we just join them anyways + var fullname = name ? PATH.resolve(PATH.join(parent, name)) : parent; + function processData(byteArray) { + function finish(byteArray) { + if (!dontCreateFile) { + FS.createDataFile(parent, name, byteArray, canRead, canWrite, canOwn); + } + if (onload) onload(); + removeRunDependency('cp ' + fullname); + } + var handled = false; + Module['preloadPlugins'].forEach(function(plugin) { + if (handled) return; + if (plugin['canHandle'](fullname)) { + plugin['handle'](byteArray, fullname, finish, function() { + if (onerror) onerror(); + removeRunDependency('cp ' + fullname); + }); + handled = true; + } + }); + if (!handled) finish(byteArray); + } + addRunDependency('cp ' + fullname); + if (typeof url == 'string') { + Browser.asyncLoad(url, function(byteArray) { + processData(byteArray); + }, onerror); + } else { + processData(url); + } + }, + + // + // persistence + // + indexedDB: function() { + return window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; + }, + + DB_NAME: function() { + return 'EM_FS_' + window.location.pathname; + }, + DB_VERSION: 20, + DB_STORE_NAME: 'FILE_DATA', + + // asynchronously saves a list of files to an IndexedDB. The DB will be created if not already existing. + saveFilesToDB: function(paths, onload, onerror) { + onload = onload || function(){}; + onerror = onerror || function(){}; + var indexedDB = FS.indexedDB(); + try { + var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION); + } catch (e) { + return onerror(e); + } + openRequest.onupgradeneeded = function() { + console.log('creating db'); + var db = openRequest.result; + db.createObjectStore(FS.DB_STORE_NAME); + }; + openRequest.onsuccess = function() { + var db = openRequest.result; + var transaction = db.transaction([FS.DB_STORE_NAME], 'readwrite'); + var files = transaction.objectStore(FS.DB_STORE_NAME); + var ok = 0, fail = 0, total = paths.length; + function finish() { + if (fail == 0) onload(); else onerror(); + } + paths.forEach(function(path) { + var putRequest = files.put(FS.analyzePath(path).object.contents, path); + putRequest.onsuccess = function() { ok++; if (ok + fail == total) finish() }; + putRequest.onerror = function() { fail++; if (ok + fail == total) finish() }; + }); + transaction.onerror = onerror; + }; + openRequest.onerror = onerror; + }, + + // asychronously loads a file from IndexedDB. + loadFilesFromDB: function(paths, onload, onerror) { + onload = onload || function(){}; + onerror = onerror || function(){}; + var indexedDB = FS.indexedDB(); + try { + var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION); + } catch (e) { + return onerror(e); + } + openRequest.onupgradeneeded = onerror; // no database to load from + openRequest.onsuccess = function() { + var db = openRequest.result; + try { + var transaction = db.transaction([FS.DB_STORE_NAME], 'readonly'); + } catch(e) { + onerror(e); + return; + } + var files = transaction.objectStore(FS.DB_STORE_NAME); + var ok = 0, fail = 0, total = paths.length; + function finish() { + if (fail == 0) onload(); else onerror(); + } + paths.forEach(function(path) { + var getRequest = files.get(path); + getRequest.onsuccess = function() { + if (FS.analyzePath(path).exists) { + FS.unlink(path); + } + FS.createDataFile(PATH.dirname(path), PATH.basename(path), getRequest.result, true, true, true); + ok++; + if (ok + fail == total) finish(); + }; + getRequest.onerror = function() { fail++; if (ok + fail == total) finish() }; + }); + transaction.onerror = onerror; + }; + openRequest.onerror = onerror; } } }); diff --git a/src/library_gl.js b/src/library_gl.js index c134ad97..16ea5531 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -657,6 +657,20 @@ var LibraryGL = { glBufferData__sig: 'viiii', glBufferData: function(target, size, data, usage) { + switch (usage) { // fix usages, WebGL only has *_DRAW + case 0x88E1: // GL_STREAM_READ + case 0x88E2: // GL_STREAM_COPY + usage = 0x88E0; // GL_STREAM_DRAW + break; + case 0x88E5: // GL_STATIC_READ + case 0x88E6: // GL_STATIC_COPY + usage = 0x88E4; // GL_STATIC_DRAW + break; + case 0x88E9: // GL_DYNAMIC_READ + case 0x88EA: // GL_DYNAMIC_COPY + usage = 0x88E8; // GL_DYNAMIC_DRAW + break; + } Module.ctx.bufferData(target, HEAPU8.subarray(data, data+size), usage); }, @@ -3398,6 +3412,7 @@ var LibraryGL = { // does not work for glBegin/End, where we generate renderer components dynamically and then // disable them ourselves, but it does help with glDrawElements/Arrays. if (!this.modifiedClientAttributes) { + GL.immediate.vertexCounter = (GL.immediate.stride * count) / 4; // XXX assuming float return; } this.modifiedClientAttributes = false; diff --git a/src/library_openal.js b/src/library_openal.js index dea986f3..e8a2e223 100644 --- a/src/library_openal.js +++ b/src/library_openal.js @@ -8,12 +8,6 @@ var LibraryOpenAL = { QUEUE_INTERVAL: 25, QUEUE_LOOKAHEAD: 100, - getCurrentTime: function(context) { - // currentTime is frozen during execution, use performance timers to - // emulate the current time at call time - return (window['performance']['now']() - context.startTime) / 1000.0; - }, - updateSources: function(context) { for (var i = 0; i < context.src.length; i++) { AL.updateSource(context.src[i]); @@ -28,7 +22,7 @@ var LibraryOpenAL = { return; } - var currentTime = AL.getCurrentTime(AL.currentContext); + var currentTime = AL.currentContext.ctx.currentTime; var startTime = src.bufferPosition; for (var i = src.buffersPlayed; i < src.queue.length; i++) { @@ -79,7 +73,7 @@ var LibraryOpenAL = { if (src.state !== 0x1013 /* AL_PAUSED */) { src.state = 0x1012 /* AL_PLAYING */; // Reset our position. - src.bufferPosition = AL.getCurrentTime(AL.currentContext); + src.bufferPosition = AL.currentContext.ctx.currentTime; src.buffersPlayed = 0; #if OPENAL_DEBUG console.log('setSourceState resetting and playing source ' + idx); @@ -87,7 +81,7 @@ var LibraryOpenAL = { } else { src.state = 0x1012 /* AL_PLAYING */; // Use the current offset from src.bufferPosition to resume at the correct point. - src.bufferPosition = AL.getCurrentTime(AL.currentContext) - src.bufferPosition; + src.bufferPosition = AL.currentContext.ctx.currentTime - src.bufferPosition; #if OPENAL_DEBUG console.log('setSourceState resuming source ' + idx + ' at ' + src.bufferPosition.toFixed(4)); #endif @@ -98,7 +92,7 @@ var LibraryOpenAL = { if (src.state === 0x1012 /* AL_PLAYING */) { src.state = 0x1013 /* AL_PAUSED */; // Store off the current offset to restore with on resume. - src.bufferPosition = AL.getCurrentTime(AL.currentContext) - src.bufferPosition; + src.bufferPosition = AL.currentContext.ctx.currentTime - src.bufferPosition; AL.stopSourceQueue(src); #if OPENAL_DEBUG console.log('setSourceState pausing source ' + idx + ' at ' + src.bufferPosition.toFixed(4)); @@ -212,8 +206,7 @@ var LibraryOpenAL = { err: 0, src: [], buf: [], - interval: setInterval(function() { AL.updateSources(context); }, AL.QUEUE_INTERVAL), - startTime: window['performance']['now']() + interval: setInterval(function() { AL.updateSources(context); }, AL.QUEUE_INTERVAL) }; AL.contexts.push(context); return AL.contexts.length; diff --git a/src/library_sdl.js b/src/library_sdl.js index 9231f41b..7c0593b2 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -883,6 +883,14 @@ var LibrarySDL = { surfData.locked++; if (surfData.locked > 1) return 0; + // Mark in C/C++-accessible SDL structure + // SDL_Surface has the following fields: Uint32 flags, SDL_PixelFormat *format; int w, h; Uint16 pitch; void *pixels; ... + // So we have fields all of the same size, and 5 of them before us. + // TODO: Use macros like in library.js + {{{ makeSetValue('surf', '5*Runtime.QUANTUM_SIZE', 'surfData.buffer', 'void*') }}}; + + if (surf == SDL.screen && Module.screenIsReadOnly && surfData.image) return 0; + surfData.image = surfData.ctx.getImageData(0, 0, surfData.width, surfData.height); if (surf == SDL.screen) { var data = surfData.image.data; @@ -925,12 +933,6 @@ var LibrarySDL = { } } - // Mark in C/C++-accessible SDL structure - // SDL_Surface has the following fields: Uint32 flags, SDL_PixelFormat *format; int w, h; Uint16 pitch; void *pixels; ... - // So we have fields all of the same size, and 5 of them before us. - // TODO: Use macros like in library.js - {{{ makeSetValue('surf', '5*Runtime.QUANTUM_SIZE', 'surfData.buffer', 'void*') }}}; - return 0; }, @@ -1271,6 +1273,11 @@ var LibrarySDL = { return 1; }, + SDL_SetPalette__deps: ['SDL_SetColors'], + SDL_SetPalette: function(surf, flags, colors, firstColor, nColors) { + return _SDL_SetColors(surf, colors, firstColor, nColors); + }, + SDL_MapRGB: function(fmt, r, g, b) { // Canvas screens are always RGBA. We assume the machine is little-endian. return r&0xff|(g&0xff)<<8|(b&0xff)<<16|0xff000000; @@ -2276,7 +2283,7 @@ var LibrarySDL = { SDL_CondWaitTimeout: function() { throw 'SDL_CondWaitTimeout: TODO' }, SDL_WM_IconifyWindow: function() { throw 'SDL_WM_IconifyWindow TODO' }, - Mix_SetPostMix: function() { throw 'Mix_SetPostMix: TODO' }, + Mix_SetPostMix: function() { Runtime.warnOnce('Mix_SetPostMix: TODO') }, Mix_QuerySpec: function() { throw 'Mix_QuerySpec: TODO' }, Mix_FadeInChannelTimed: function() { throw 'Mix_FadeInChannelTimed' }, Mix_FadeOutChannel: function() { throw 'Mix_FadeOutChannel' }, diff --git a/src/modules.js b/src/modules.js index 373e60d9..1a931572 100644 --- a/src/modules.js +++ b/src/modules.js @@ -227,6 +227,8 @@ var Types = { needAnalysis: {}, // Types noticed during parsing, that need analysis + hasInlineJS: false, // whether the program has inline JS anywhere + // Set to true if we actually use precise i64 math: If PRECISE_I64_MATH is set, and also such math is actually // needed (+,-,*,/,% - we do not need it for bitops), or PRECISE_I64_MATH is 2 (forced) preciseI64MathUsed: (PRECISE_I64_MATH == 2) @@ -467,7 +469,10 @@ var PassManager = { })); } else if (phase == 'funcs') { print('\n//FORWARDED_DATA:' + JSON.stringify({ - Types: { preciseI64MathUsed: Types.preciseI64MathUsed }, + Types: { + hasInlineJS: Types.hasInlineJS, + preciseI64MathUsed: Types.preciseI64MathUsed + }, Functions: { blockAddresses: Functions.blockAddresses, indexedFunctions: Functions.indexedFunctions, diff --git a/src/parseTools.js b/src/parseTools.js index 66354dca..90c5acab 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -35,8 +35,13 @@ function preprocess(text) { var op = parts[2]; var value = parts[3]; if (op) { - assert(op === '==') - showStack.push(ident in this && this[ident] == value); + if (op === '==') { + showStack.push(ident in this && this[ident] == value); + } else if (op === '!=') { + showStack.push(!(ident in this && this[ident] == value)); + } else { + error('unsupported preprecessor op ' + op); + } } else { showStack.push(ident in this && this[ident] > 0); } diff --git a/src/postamble.js b/src/postamble.js index df844121..d3212767 100644 --- a/src/postamble.js +++ b/src/postamble.js @@ -10,8 +10,8 @@ ExitStatus.prototype = new Error(); ExitStatus.prototype.constructor = ExitStatus; var initialStackTop; - var preloadStartTime = null; +var calledMain = false; Module['callMain'] = Module.callMain = function callMain(args) { assert(runDependencies == 0, 'cannot call main when async dependencies remain! (listen on __ATMAIN__)'); @@ -70,6 +70,8 @@ Module['callMain'] = Module.callMain = function callMain(args) { } else { throw e; } + } finally { + calledMain = true; } } diff --git a/src/preamble.js b/src/preamble.js index 4cae05a6..227b3043 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -595,9 +595,9 @@ var DYNAMIC_BASE = 0, DYNAMICTOP = 0; // dynamic area handled by sbrk function enlargeMemory() { #if ALLOW_MEMORY_GROWTH == 0 #if ASM_JS == 0 - abort('Cannot enlarge memory arrays. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value, (2) compile with ALLOW_MEMORY_GROWTH which adjusts the size at runtime but prevents some optimizations, or (3) set Module.TOTAL_MEMORY before the program runs.'); + abort('Cannot enlarge memory arrays. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value ' + TOTAL_MEMORY + ', (2) compile with ALLOW_MEMORY_GROWTH which adjusts the size at runtime but prevents some optimizations, or (3) set Module.TOTAL_MEMORY before the program runs.'); #else - abort('Cannot enlarge memory arrays in asm.js. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value, or (2) set Module.TOTAL_MEMORY before the program runs.'); + abort('Cannot enlarge memory arrays in asm.js. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value ' + TOTAL_MEMORY + ', or (2) set Module.TOTAL_MEMORY before the program runs.'); #endif #else // TOTAL_MEMORY is the current size of the actual array, and DYNAMICTOP is the new top. diff --git a/src/proxyClient.js b/src/proxyClient.js new file mode 100644 index 00000000..41f6a501 --- /dev/null +++ b/src/proxyClient.js @@ -0,0 +1,71 @@ + +// proxy to/from worker + +Module.ctx = Module.canvas.getContext('2d'); + +var worker = new Worker('{{{ filename }}}.js'); + +worker.onmessage = function(event) { + var data = event.data; + switch (data.target) { + case 'stdout': { + Module.print(data.content); + break; + } + case 'stderr': { + Module.printErr(data.content); + break; + } + case 'window': { + window[data.method](); + break; + } + case 'canvas': { + switch (data.op) { + case 'resize': { + Module.canvas.width = data.width; + Module.canvas.height = data.height; + Module.canvasData = Module.ctx.getImageData(0, 0, data.width, data.height); + break; + } + case 'render': { + Module.canvasData.data.set(data.image.data); + Module.ctx.putImageData(Module.canvasData, 0, 0); + break; + } + default: throw 'eh?'; + } + break; + } + default: throw 'what?'; + } +}; + +function cloneEvent(event) { + var ret = {}; + for (var x in event) { + if (x == x.toUpperCase()) continue; + var prop = event[x]; + if (typeof prop === 'number' || typeof prop === 'string') ret[x] = prop; + } + return ret; +}; + +['keydown', 'keyup', 'keypress', 'blur', 'visibilitychange'].forEach(function(event) { + document.addEventListener(event, function(event) { + worker.postMessage({ target: 'document', event: cloneEvent(event) }); + }); +}); + +['unload'].forEach(function(event) { + window.addEventListener(event, function(event) { + worker.postMessage({ target: 'window', event: cloneEvent(event) }); + }); +}); + +['mousedown', 'mouseup', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mouseout'].forEach(function(event) { + Module.canvas.addEventListener(event, function(event) { + worker.postMessage({ target: 'canvas', event: cloneEvent(event) }); + }, true); +}); + diff --git a/src/proxyWorker.js b/src/proxyWorker.js new file mode 100644 index 00000000..8d641b0d --- /dev/null +++ b/src/proxyWorker.js @@ -0,0 +1,128 @@ + +function EventListener() { + this.listeners = {}; + + this.addEventListener = function(event, func) { + if (!this.listeners[event]) this.listeners[event] = []; + this.listeners[event].push(func); + }; + + this.fireEvent = function(event) { + event.preventDefault = function(){}; + + if (event.type in this.listeners) { + this.listeners[event.type].forEach(function(listener) { + listener(event); + }); + } + }; +}; + +var window = this; +var windowExtra = new EventListener(); +for (var x in windowExtra) window[x] = windowExtra[x]; + +window.close = function() { + postMessage({ target: 'window', method: 'close' }); +}; + +var document = new EventListener(); + +document.createElement = function(what) { + switch(what) { + case 'canvas': { + var canvas = new EventListener(); + canvas.ensureData = function() { + if (!canvas.data || canvas.data.width !== canvas.width || canvas.data.height !== canvas.height) { + canvas.data = { + width: canvas.width, + height: canvas.height, + data: new Uint8Array(canvas.width*canvas.height*4) + }; + postMessage({ target: 'canvas', op: 'resize', width: canvas.width, height: canvas.height }); + } + }; + canvas.getContext = function(type) { + assert(type == '2d'); + return { + getImageData: function(x, y, w, h) { + assert(x == 0 && y == 0 && w == canvas.width && h == canvas.height); + canvas.ensureData(); + return { + width: canvas.data.width, + height: canvas.data.height, + data: new Uint8Array(canvas.data.data) // TODO: can we avoid this copy? + }; + }, + putImageData: function(image, x, y) { + canvas.ensureData(); + assert(x == 0 && y == 0 && image.width == canvas.width && image.height == canvas.height); + canvas.data.data.set(image.data); // TODO: can we avoid this copy? + postMessage({ target: 'canvas', op: 'render', image: canvas.data }); + } + }; + }; + return canvas; + } + default: throw 'document.createElement ' + what; + } +}; + +var console = { + log: function(x) { + Module.printErr(x); + } +}; + +Module.canvas = document.createElement('canvas'); + +Module.setStatus = function(){}; + +Module.print = function(x) { + postMessage({ target: 'stdout', content: x }); +}; +Module.printErr = function(x) { + postMessage({ target: 'stderr', content: x }); +}; + +// buffer messages until the program starts to run + +var messageBuffer = null; + +function messageResender() { + if (calledMain) { + assert(messageBuffer && messageBuffer.length > 0); + messageBuffer.forEach(function(message) { + onmessage(message); + }); + messageBuffer = null; + } else { + setTimeout(messageResender, 100); + } +} + +onmessage = function(message) { + if (!calledMain) { + if (!messageBuffer) { + messageBuffer = []; + setTimeout(messageResender, 100); + } + messageBuffer.push(message); + } + switch (message.data.target) { + case 'document': { + document.fireEvent(message.data.event); + break; + } + case 'window': { + window.fireEvent(message.data.event); + break; + } + case 'canvas': { + Module.canvas.fireEvent(message.data.event); + break; + } + default: throw 'wha? ' + message.data.target; + } +}; + diff --git a/src/settings.js b/src/settings.js index 79c54c2b..1ef0cd58 100644 --- a/src/settings.js +++ b/src/settings.js @@ -343,6 +343,9 @@ var RUNTIME_LINKED_LIBS = []; // If this is a main file (BUILD_AS_SHARED_LIB == // linked libraries can break things. var BUILD_AS_WORKER = 0; // If set to 1, this is a worker library, a special kind of library // that is run in a worker. See emscripten.h +var PROXY_TO_WORKER = 0; // If set to 1, we build the project into a js file that will run + // in a worker, and generate an html file that proxies input and + // output to/from it. var LINKABLE = 0; // If set to 1, this file can be linked with others, either as a shared // library or as the main file that calls a shared library. To enable that, // we will not internalize all symbols and cull the unused ones, in other diff --git a/system/bin/sdl-config b/system/bin/sdl-config new file mode 100755 index 00000000..51dff31d --- /dev/null +++ b/system/bin/sdl-config @@ -0,0 +1,13 @@ +#!/usr/bin/env python2 + +import sys + +print >> sys.stderr, 'emscripten sdl-config called with', sys.argv + +args = sys.argv[1:] + +if args[0] == '--cflags': + print '' +elif '--version' in args: + print '1.3.0' + diff --git a/system/include/libcxx/__locale b/system/include/libcxx/__locale index 93147ec0..5ae8fa59 100644 --- a/system/include/libcxx/__locale +++ b/system/include/libcxx/__locale @@ -21,9 +21,9 @@ #include <locale.h> #ifdef _LIBCPP_MSVCRT # include <support/win32/locale_win32.h> -#elif (defined(__GLIBC__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__sun__)) || defined(EMSCRIPTEN) +#elif (defined(__GLIBC__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__sun__)) || defined(__EMSCRIPTEN__) # include <xlocale.h> -#endif // _WIN32 || __GLIBC__ || __APPLE__ || __FreeBSD__ || __sun__ || EMSCRIPTEN +#endif // _WIN32 || __GLIBC__ || __APPLE__ || __FreeBSD__ || __sun__ || __EMSCRIPTEN__ #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) #pragma GCC system_header @@ -339,12 +339,12 @@ public: static const mask punct = _PUNCT; static const mask xdigit = _HEX; static const mask blank = _BLANK; -#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(EMSCRIPTEN) || defined(__NetBSD__) +#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__EMSCRIPTEN__) || defined(__NetBSD__) #ifdef __APPLE__ typedef __uint32_t mask; #elif defined(__FreeBSD__) typedef unsigned long mask; -#elif defined(EMSCRIPTEN) || defined(__NetBSD__) +#elif defined(__EMSCRIPTEN__) || defined(__NetBSD__) typedef unsigned short mask; #endif static const mask space = _CTYPE_S; @@ -373,7 +373,7 @@ public: static const mask punct = _ISPUNCT; static const mask xdigit = _ISXDIGIT; static const mask blank = _ISBLANK; -#else // __GLIBC__ || _WIN32 || __APPLE__ || __FreeBSD__ || EMSCRIPTEN || __sun__ +#else // __GLIBC__ || _WIN32 || __APPLE__ || __FreeBSD__ || __EMSCRIPTEN__ || __sun__ typedef unsigned long mask; static const mask space = 1<<0; static const mask print = 1<<1; @@ -596,7 +596,7 @@ public: #endif _LIBCPP_ALWAYS_INLINE const mask* table() const _NOEXCEPT {return __tab_;} static const mask* classic_table() _NOEXCEPT; -#if defined(__GLIBC__) || defined(EMSCRIPTEN) +#if defined(__GLIBC__) || defined(__EMSCRIPTEN__) static const int* __classic_upper_table() _NOEXCEPT; static const int* __classic_lower_table() _NOEXCEPT; #endif diff --git a/system/include/libcxx/locale b/system/include/libcxx/locale index 00a275f9..f5f5fff9 100644 --- a/system/include/libcxx/locale +++ b/system/include/libcxx/locale @@ -224,7 +224,7 @@ typedef _VSTD::unique_ptr<__locale_struct, decltype(&uselocale)> __locale_raii; // OSX has nice foo_l() functions that let you turn off use of the global // locale. Linux, not so much. The following functions avoid the locale when // that's possible and otherwise do the wrong thing. FIXME. -#if defined(__linux__) || defined(EMSCRIPTEN) +#if defined(__linux__) || defined(__EMSCRIPTEN__) #ifdef _LIBCPP_LOCALE__L_EXTENSIONS decltype(MB_CUR_MAX_L(_VSTD::declval<locale_t>())) diff --git a/system/lib/libcxx/exception.cpp b/system/lib/libcxx/exception.cpp index d3e1b292..3487bd8b 100644 --- a/system/lib/libcxx/exception.cpp +++ b/system/lib/libcxx/exception.cpp @@ -78,7 +78,7 @@ get_terminate() _NOEXCEPT return __sync_fetch_and_add(&__terminate_handler, (terminate_handler)0); } -#ifndef EMSCRIPTEN // We provide this in JS +#ifndef __EMSCRIPTEN__ // We provide this in JS _LIBCPP_NORETURN void terminate() _NOEXCEPT @@ -101,10 +101,10 @@ terminate() _NOEXCEPT } #endif // _LIBCPP_NO_EXCEPTIONS } -#endif // !EMSCRIPTEN +#endif // !__EMSCRIPTEN__ #endif // !defined(LIBCXXRT) && !defined(_LIBCPPABI_VERSION) -#if !defined(LIBCXXRT) && !defined(__GLIBCXX__) && !defined(EMSCRIPTEN) +#if !defined(LIBCXXRT) && !defined(__GLIBCXX__) && !defined(__EMSCRIPTEN__) bool uncaught_exception() _NOEXCEPT { #if defined(__APPLE__) || defined(_LIBCPPABI_VERSION) diff --git a/system/lib/libcxx/locale.cpp b/system/lib/libcxx/locale.cpp index d95d0c9c..ad64668f 100644 --- a/system/lib/libcxx/locale.cpp +++ b/system/lib/libcxx/locale.cpp @@ -792,7 +792,7 @@ ctype<wchar_t>::do_toupper(char_type c) const { #ifdef _LIBCPP_HAS_DEFAULTRUNELOCALE return isascii(c) ? _DefaultRuneLocale.__mapupper[c] : c; -#elif defined(__GLIBC__) || defined(EMSCRIPTEN) || defined(__NetBSD__) +#elif defined(__GLIBC__) || defined(__EMSCRIPTEN__) || defined(__NetBSD__) return isascii(c) ? ctype<char>::__classic_upper_table()[c] : c; #else return (isascii(c) && iswlower_l(c, __cloc())) ? c-L'a'+L'A' : c; @@ -805,7 +805,7 @@ ctype<wchar_t>::do_toupper(char_type* low, const char_type* high) const for (; low != high; ++low) #ifdef _LIBCPP_HAS_DEFAULTRUNELOCALE *low = isascii(*low) ? _DefaultRuneLocale.__mapupper[*low] : *low; -#elif defined(__GLIBC__) || defined(EMSCRIPTEN) || defined(__NetBSD__) +#elif defined(__GLIBC__) || defined(__EMSCRIPTEN__) || defined(__NetBSD__) *low = isascii(*low) ? ctype<char>::__classic_upper_table()[*low] : *low; #else @@ -819,7 +819,7 @@ ctype<wchar_t>::do_tolower(char_type c) const { #ifdef _LIBCPP_HAS_DEFAULTRUNELOCALE return isascii(c) ? _DefaultRuneLocale.__maplower[c] : c; -#elif defined(__GLIBC__) || defined(EMSCRIPTEN) || defined(__NetBSD__) +#elif defined(__GLIBC__) || defined(__EMSCRIPTEN__) || defined(__NetBSD__) return isascii(c) ? ctype<char>::__classic_lower_table()[c] : c; #else return (isascii(c) && isupper_l(c, __cloc())) ? c-L'A'+'a' : c; @@ -832,7 +832,7 @@ ctype<wchar_t>::do_tolower(char_type* low, const char_type* high) const for (; low != high; ++low) #ifdef _LIBCPP_HAS_DEFAULTRUNELOCALE *low = isascii(*low) ? _DefaultRuneLocale.__maplower[*low] : *low; -#elif defined(__GLIBC__) || defined(EMSCRIPTEN) || defined(__NetBSD__) +#elif defined(__GLIBC__) || defined(__EMSCRIPTEN__) || defined(__NetBSD__) *low = isascii(*low) ? ctype<char>::__classic_lower_table()[*low] : *low; #else @@ -901,7 +901,7 @@ ctype<char>::do_toupper(char_type c) const static_cast<char>(_DefaultRuneLocale.__mapupper[static_cast<ptrdiff_t>(c)]) : c; #elif defined(__NetBSD__) return static_cast<char>(__classic_upper_table()[static_cast<unsigned char>(c)]); -#elif defined(__GLIBC__) || defined(EMSCRIPTEN) +#elif defined(__GLIBC__) || defined(__EMSCRIPTEN__) return isascii(c) ? static_cast<char>(__classic_upper_table()[static_cast<unsigned char>(c)]) : c; #else @@ -918,7 +918,7 @@ ctype<char>::do_toupper(char_type* low, const char_type* high) const static_cast<char>(_DefaultRuneLocale.__mapupper[static_cast<ptrdiff_t>(*low)]) : *low; #elif defined(__NetBSD__) *low = static_cast<char>(__classic_upper_table()[static_cast<unsigned char>(*low)]); -#elif defined(__GLIBC__) || defined(EMSCRIPTEN) +#elif defined(__GLIBC__) || defined(__EMSCRIPTEN__) *low = isascii(*low) ? static_cast<char>(__classic_upper_table()[static_cast<size_t>(*low)]) : *low; #else @@ -935,7 +935,7 @@ ctype<char>::do_tolower(char_type c) const static_cast<char>(_DefaultRuneLocale.__maplower[static_cast<ptrdiff_t>(c)]) : c; #elif defined(__NetBSD__) return static_cast<char>(__classic_lower_table()[static_cast<unsigned char>(c)]); -#elif defined(__GLIBC__) || defined(EMSCRIPTEN) || defined(__NetBSD__) +#elif defined(__GLIBC__) || defined(__EMSCRIPTEN__) || defined(__NetBSD__) return isascii(c) ? static_cast<char>(__classic_lower_table()[static_cast<size_t>(c)]) : c; #else @@ -951,7 +951,7 @@ ctype<char>::do_tolower(char_type* low, const char_type* high) const *low = isascii(*low) ? static_cast<char>(_DefaultRuneLocale.__maplower[static_cast<ptrdiff_t>(*low)]) : *low; #elif defined(__NetBSD__) *low = static_cast<char>(__classic_lower_table()[static_cast<unsigned char>(*low)]); -#elif defined(__GLIBC__) || defined(EMSCRIPTEN) +#elif defined(__GLIBC__) || defined(__EMSCRIPTEN__) *low = isascii(*low) ? static_cast<char>(__classic_lower_table()[static_cast<size_t>(*low)]) : *low; #else *low = (isascii(*low) && isupper_l(*low, __cloc())) ? *low-'A'+'a' : *low; @@ -992,7 +992,7 @@ ctype<char>::do_narrow(const char_type* low, const char_type* high, char dfault, return low; } -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ extern "C" const unsigned short ** __ctype_b_loc(); extern "C" const int ** __ctype_tolower_loc(); extern "C" const int ** __ctype_toupper_loc(); @@ -1013,7 +1013,7 @@ ctype<char>::classic_table() _NOEXCEPT return _ctype+1; // internal ctype mask table defined in msvcrt.dll // This is assumed to be safe, which is a nonsense assumption because we're // going to end up dereferencing it later... -#elif defined(EMSCRIPTEN) +#elif defined(__EMSCRIPTEN__) return *__ctype_b_loc(); #else // Platform not supported: abort so the person doing the port knows what to @@ -1050,7 +1050,7 @@ ctype<char>::__classic_upper_table() _NOEXCEPT return _C_toupper_tab_ + 1; } -#elif defined(EMSCRIPTEN) +#elif defined(__EMSCRIPTEN__) const int* ctype<char>::__classic_lower_table() _NOEXCEPT { @@ -1062,7 +1062,7 @@ ctype<char>::__classic_upper_table() _NOEXCEPT { return *__ctype_toupper_loc(); } -#endif // __GLIBC__ || EMSCRIPTEN || __NETBSD__ +#endif // __GLIBC__ || __EMSCRIPTEN__ || __NETBSD__ // template <> class ctype_byname<char> diff --git a/system/lib/libcxxabi/src/cxa_new_delete.cpp b/system/lib/libcxxabi/src/cxa_new_delete.cpp index e6fee5f2..7fd0b3b7 100644 --- a/system/lib/libcxxabi/src/cxa_new_delete.cpp +++ b/system/lib/libcxxabi/src/cxa_new_delete.cpp @@ -228,7 +228,7 @@ bad_array_new_length::what() const _NOEXCEPT return "bad_array_new_length"; } -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ // We don't build the new.cpp from libcxx, so we need to define this. void __throw_bad_alloc() diff --git a/tests/filesystem/src.js b/tests/filesystem/src.js index dbdd4bed..91337f5b 100644 --- a/tests/filesystem/src.js +++ b/tests/filesystem/src.js @@ -1,16 +1,18 @@ var dummy_device = FS.makedev(64, 0); FS.registerDevice(dummy_device, {}); -FS.createFolder('/', 'forbidden', false, false); -FS.createFolder('/forbidden', 'test', true, true); -FS.createPath('/', 'abc/123', true, true); -FS.createPath('/', 'abc/456', true, true); -FS.createPath('/', 'def/789', true, true); -FS.mkdev('/abc/deviceA', 0666, dummy_device); -FS.mkdev('/def/deviceB', 0666, dummy_device); -FS.createLink('/abc', 'localLink', '123', true, true); -FS.createLink('/abc', 'rootLink', '/', true, true); -FS.createLink('/abc', 'relativeLink', '../def', true, true); +FS.mkdir('/forbidden', 0000); +FS.mkdir('/forbidden/test'); +FS.mkdir('/abc'); +FS.mkdir('/abc/123'); +FS.mkdir('/abc/456'); +FS.mkdir('/def'); +FS.mkdir('/def/789'); +FS.mkdev('/abc/deviceA', dummy_device); +FS.mkdev('/def/deviceB', dummy_device); +FS.symlink('123', '/abc/localLink'); +FS.symlink('/', '/abc/rootLink'); +FS.symlink('../def', '/abc/relativeLink'); FS.ignorePermissions = false; function explore(path) { diff --git a/tests/gl_vertex_buffer.c b/tests/gl_vertex_buffer.c new file mode 100644 index 00000000..6b695462 --- /dev/null +++ b/tests/gl_vertex_buffer.c @@ -0,0 +1,195 @@ +/******************************************************************* + * * + * Using SDL With OpenGL * + * * + * Tutorial by Kyle Foley (sdw) * + * * + * http://gpwiki.org/index.php/SDL:Tutorials:Using_SDL_with_OpenGL * + * * + *******************************************************************/ + +/* + THIS WORK, INCLUDING THE SOURCE CODE, DOCUMENTATION + AND RELATED MEDIA AND DATA, IS PLACED INTO THE PUBLIC DOMAIN. + + THE ORIGINAL AUTHOR IS KYLE FOLEY. + + THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY + OF ANY KIND, NOT EVEN THE IMPLIED WARRANTY OF + MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE, + ASSUMES _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE + RESULTING FROM THE USE, MODIFICATION, OR + REDISTRIBUTION OF THIS SOFTWARE. + */ + +#if !EMSCRIPTEN +#define USE_GLEW 0 +#endif + +#if USE_GLEW +#include "GL/glew.h" +#endif + +#include <SDL/SDL.h> + +#if !USE_GLEW +#include "SDL/SDL_opengl.h" +#endif + +#include <stdio.h> +#include <string.h> +#include <assert.h> + +int main(int argc, char *argv[]) +{ + SDL_Surface *screen; + + // Slightly different SDL initialization + if ( SDL_Init(SDL_INIT_VIDEO) != 0 ) { + printf("Unable to initialize SDL: %s\n", SDL_GetError()); + return 1; + } + + SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); // *new* + + screen = SDL_SetVideoMode( 640, 480, 16, SDL_OPENGL ); // *changed* + if ( !screen ) { + printf("Unable to set video mode: %s\n", SDL_GetError()); + return 1; + } + + // Set the OpenGL state after creating the context with SDL_SetVideoMode + + glClearColor( 0, 0, 0, 0 ); + +#if !EMSCRIPTEN + glEnable( GL_TEXTURE_2D ); // Need this to display a texture XXX unnecessary in OpenGL ES 2.0/WebGL +#endif + + glViewport( 0, 0, 640, 480 ); + + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); + + // Clear the screen before drawing + glClear( GL_COLOR_BUFFER_BIT ); + + typedef struct Color { + GLubyte r; + GLubyte g; + GLubyte b; + GLubyte a; + } Color; + + typedef struct Vertex { + GLfloat x; + GLfloat y; + Color color; + } Vertex; + + Vertex vertices[18] = { + {-1.00, 0.0, {0xFF, 0x00, 0xFF, 0xFF}}, + {-1.00, 1.0, {0xFF, 0xFF, 0x00, 0xFF}}, + {-0.75, 0.0, {0xFF, 0x00, 0x00, 0xFF}}, + {-0.75, 1.0, {0xFF, 0xFF, 0xFF, 0xFF}}, + {-0.50, 0.0, {0xFF, 0x00, 0x00, 0xFF}}, + {-0.50, 1.0, {0xFF, 0xFF, 0x00, 0xFF}}, + {-0.25, 0.0, {0xFF, 0x00, 0xFF, 0xFF}}, + {-0.25, 1.0, {0xFF, 0xFF, 0x00, 0xFF}}, + {-0.00, 0.0, {0xFF, 0x00, 0x00, 0xFF}}, + {-0.00, 1.0, {0xFF, 0xFF, 0xFF, 0xFF}}, + { 0.25, 0.0, {0xFF, 0x00, 0x00, 0xFF}}, + { 0.25, 1.0, {0xFF, 0xFF, 0x00, 0xFF}}, + { 0.50, 0.0, {0xFF, 0x00, 0xFF, 0xFF}}, + { 0.50, 1.0, {0xFF, 0xFF, 0x00, 0xFF}}, + { 0.75, 0.0, {0xFF, 0x00, 0x00, 0xFF}}, + { 0.75, 1.0, {0xFF, 0xFF, 0xFF, 0xFF}}, + { 1.00, 0.0, {0xFF, 0x00, 0x00, 0xFF}}, + { 1.00, 1.0, {0xFF, 0xFF, 0x00, 0xFF}} + }; + + Vertex vertices2[18] = { + {-1.00, -1.0, {0xFF, 0x00, 0xFF, 0xFF}}, + {-1.00, 0.0, {0xFF, 0xFF, 0x00, 0xFF}}, + {-0.75, -1.0, {0xFF, 0x00, 0x00, 0xFF}}, + {-0.75, 0.0, {0xFF, 0xFF, 0xFF, 0xFF}}, + {-0.50, -1.0, {0xFF, 0x00, 0x00, 0xFF}}, + {-0.50, 0.0, {0xFF, 0xFF, 0x00, 0xFF}}, + {-0.25, -1.0, {0xFF, 0x00, 0xFF, 0xFF}}, + {-0.25, 0.0, {0xFF, 0xFF, 0x00, 0xFF}}, + {-0.00, -1.0, {0xFF, 0x00, 0x00, 0xFF}}, + {-0.00, 0.0, {0xFF, 0xFF, 0xFF, 0xFF}}, + { 0.25, -1.0, {0xFF, 0x00, 0x00, 0xFF}}, + { 0.25, 0.0, {0xFF, 0xFF, 0x00, 0xFF}}, + { 0.50, -1.0, {0xFF, 0x00, 0xFF, 0xFF}}, + { 0.50, 0.0, {0xFF, 0xFF, 0x00, 0xFF}}, + { 0.75, -1.0, {0xFF, 0x00, 0x00, 0xFF}}, + { 0.75, 0.0, {0xFF, 0xFF, 0xFF, 0xFF}}, + { 1.00, -1.0, {0xFF, 0x00, 0x00, 0xFF}}, + { 1.00, 0.0, {0xFF, 0xFF, 0x00, 0xFF}} + }; + + // make a vertex buffer for the second set of vertices + GLuint vbo = 0; + glGenBuffers(1, &vbo); + + + // bind to it + glBindBuffer(GL_ARRAY_BUFFER, vbo); + // send it to gl + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices2), vertices2, GL_STATIC_READ); // GL_STATIC_READ is not in WebGL! + + // unbind from it + glBindBuffer(GL_ARRAY_BUFFER, 0); + + + // DRAW + + // Clear the screen before drawing + glClear( GL_COLOR_BUFFER_BIT ); + + // This test ensures that we can use two separate arrays in memory for different + // attributes, and that they each can have different stride. + // The first test shows implicit striding (the zero indicates tightly packed) + // The second test shows explicit striding where the stride is passed in + // even though it also is tightly packed + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + // TEST 1 - clientside data + + glVertexPointer(2, GL_FLOAT, sizeof(Vertex), vertices); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), &vertices[0].color); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 6); + glDrawArrays(GL_TRIANGLE_STRIP, 10, 3); + + + + // TEST 2 - bind to array buffer, gl*Pointer calls are offsets into the buffer, which was previously uploaded to + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + + glVertexPointer(2, GL_FLOAT, sizeof(Vertex), 0); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), (GLvoid*)((GLvoid*)&vertices2[0].color - (GLvoid*)&vertices2[0])); + +// gldrawarrays first with a low number of vertices, then with a high number + glDrawArrays(GL_TRIANGLE_STRIP, 0, 6); + glDrawArrays(GL_TRIANGLE_STRIP, 10, 3); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + + SDL_GL_SwapBuffers(); + +#if !EMSCRIPTEN + // Wait for 3 seconds to give us a chance to see the image + SDL_Delay(3000); +#endif + + SDL_Quit(); + + return 0; +} diff --git a/tests/gl_vertex_buffer.png b/tests/gl_vertex_buffer.png Binary files differnew file mode 100644 index 00000000..3e1f2230 --- /dev/null +++ b/tests/gl_vertex_buffer.png diff --git a/tests/gl_vertex_buffer_pre.c b/tests/gl_vertex_buffer_pre.c new file mode 100644 index 00000000..84b76569 --- /dev/null +++ b/tests/gl_vertex_buffer_pre.c @@ -0,0 +1,177 @@ +/******************************************************************* + * * + * Using SDL With OpenGL * + * * + * Tutorial by Kyle Foley (sdw) * + * * + * http://gpwiki.org/index.php/SDL:Tutorials:Using_SDL_with_OpenGL * + * * + *******************************************************************/ + +/* + THIS WORK, INCLUDING THE SOURCE CODE, DOCUMENTATION + AND RELATED MEDIA AND DATA, IS PLACED INTO THE PUBLIC DOMAIN. + + THE ORIGINAL AUTHOR IS KYLE FOLEY. + + THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY + OF ANY KIND, NOT EVEN THE IMPLIED WARRANTY OF + MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE, + ASSUMES _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE + RESULTING FROM THE USE, MODIFICATION, OR + REDISTRIBUTION OF THIS SOFTWARE. + */ + +#if !EMSCRIPTEN +#define USE_GLEW 0 +#endif + +#if USE_GLEW +#include "GL/glew.h" +#endif + +#include <SDL/SDL.h> + +#if !USE_GLEW +#include "SDL/SDL_opengl.h" +#endif + +#include <stdio.h> +#include <string.h> +#include <assert.h> + +int main(int argc, char *argv[]) +{ + SDL_Surface *screen; + + // Slightly different SDL initialization + if ( SDL_Init(SDL_INIT_VIDEO) != 0 ) { + printf("Unable to initialize SDL: %s\n", SDL_GetError()); + return 1; + } + + SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); // *new* + + screen = SDL_SetVideoMode( 640, 480, 16, SDL_OPENGL ); // *changed* + if ( !screen ) { + printf("Unable to set video mode: %s\n", SDL_GetError()); + return 1; + } + + // Set the OpenGL state after creating the context with SDL_SetVideoMode + + glClearColor( 0, 0, 0, 0 ); + +#if !EMSCRIPTEN + glEnable( GL_TEXTURE_2D ); // Need this to display a texture XXX unnecessary in OpenGL ES 2.0/WebGL +#endif + + glViewport( 0, 0, 640, 480 ); + + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); + + // Clear the screen before drawing + glClear( GL_COLOR_BUFFER_BIT ); + + typedef struct Color { + GLubyte r; + GLubyte g; + GLubyte b; + GLubyte a; + } Color; + + typedef struct Vertex { + GLfloat x; + GLfloat y; + Color color; + } Vertex; + + Vertex vertices[18] = { + {-1.00, 0.0, {0xFF, 0x00, 0xFF, 0xFF}}, + {-1.00, 1.0, {0xFF, 0xFF, 0x00, 0xFF}}, + {-0.75, 0.0, {0xFF, 0x00, 0x00, 0xFF}}, + {-0.75, 1.0, {0xFF, 0xFF, 0xFF, 0xFF}}, + {-0.50, 0.0, {0xFF, 0x00, 0x00, 0xFF}}, + {-0.50, 1.0, {0xFF, 0xFF, 0x00, 0xFF}}, + {-0.25, 0.0, {0xFF, 0x00, 0xFF, 0xFF}}, + {-0.25, 1.0, {0xFF, 0xFF, 0x00, 0xFF}}, + {-0.00, 0.0, {0xFF, 0x00, 0x00, 0xFF}}, + {-0.00, 1.0, {0xFF, 0xFF, 0xFF, 0xFF}}, + { 0.25, 0.0, {0xFF, 0x00, 0x00, 0xFF}}, + { 0.25, 1.0, {0xFF, 0xFF, 0x00, 0xFF}}, + { 0.50, 0.0, {0xFF, 0x00, 0xFF, 0xFF}}, + { 0.50, 1.0, {0xFF, 0xFF, 0x00, 0xFF}}, + { 0.75, 0.0, {0xFF, 0x00, 0x00, 0xFF}}, + { 0.75, 1.0, {0xFF, 0xFF, 0xFF, 0xFF}}, + { 1.00, 0.0, {0xFF, 0x00, 0x00, 0xFF}}, + { 1.00, 1.0, {0xFF, 0xFF, 0x00, 0xFF}} + }; + + Vertex vertices2[18] = { + {-1.00, -1.0, {0xFF, 0x00, 0xFF, 0xFF}}, + {-1.00, 0.0, {0xFF, 0xFF, 0x00, 0xFF}}, + {-0.75, -1.0, {0xFF, 0x00, 0x00, 0xFF}}, + {-0.75, 0.0, {0xFF, 0xFF, 0xFF, 0xFF}}, + {-0.50, -1.0, {0xFF, 0x00, 0x00, 0xFF}}, + {-0.50, 0.0, {0xFF, 0xFF, 0x00, 0xFF}}, + {-0.25, -1.0, {0xFF, 0x00, 0xFF, 0xFF}}, + {-0.25, 0.0, {0xFF, 0xFF, 0x00, 0xFF}}, + {-0.00, -1.0, {0xFF, 0x00, 0x00, 0xFF}}, + {-0.00, 0.0, {0xFF, 0xFF, 0xFF, 0xFF}}, + { 0.25, -1.0, {0xFF, 0x00, 0x00, 0xFF}}, + { 0.25, 0.0, {0xFF, 0xFF, 0x00, 0xFF}}, + { 0.50, -1.0, {0xFF, 0x00, 0xFF, 0xFF}}, + { 0.50, 0.0, {0xFF, 0xFF, 0x00, 0xFF}}, + { 0.75, -1.0, {0xFF, 0x00, 0x00, 0xFF}}, + { 0.75, 0.0, {0xFF, 0xFF, 0xFF, 0xFF}}, + { 1.00, -1.0, {0xFF, 0x00, 0x00, 0xFF}}, + { 1.00, 0.0, {0xFF, 0xFF, 0x00, 0xFF}} + }; + + // make a vertex buffer for the second set of vertices + GLuint vbo = 0; + glGenBuffers(1, &vbo); + + + // bind to it + glBindBuffer(GL_ARRAY_BUFFER, vbo); + // send it to gl + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices2), vertices2, GL_STATIC_DRAW); + + // unbind from it + glBindBuffer(GL_ARRAY_BUFFER, 0); + + + // DRAW + + // Clear the screen before drawing + glClear( GL_COLOR_BUFFER_BIT ); + + // This test ensures that we can use two separate arrays in memory for different + // attributes, and that they each can have different stride. + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + glVertexPointer(2, GL_FLOAT, sizeof(Vertex), vertices); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), &vertices[0].color); + glDrawArrays(GL_TRIANGLE_STRIP, 10, 3); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 6); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + + SDL_GL_SwapBuffers(); + +#if !EMSCRIPTEN + // Wait for 3 seconds to give us a chance to see the image + SDL_Delay(3000); +#endif + + SDL_Quit(); + + return 0; +} diff --git a/tests/gl_vertex_buffer_pre.png b/tests/gl_vertex_buffer_pre.png Binary files differnew file mode 100644 index 00000000..5677a868 --- /dev/null +++ b/tests/gl_vertex_buffer_pre.png diff --git a/tests/runner.py b/tests/runner.py index f0e61c4e..ddc97ea4 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -254,7 +254,7 @@ process(sys.argv[1]) os.chdir(cwd) out = open(stdout, 'r').read() err = open(stderr, 'r').read() - if engine == SPIDERMONKEY_ENGINE and Settings.ASM_JS: + if engine == SPIDERMONKEY_ENGINE and Settings.ASM_JS == 1: err = self.validate_asmjs(err) if output_nicerizer: ret = output_nicerizer(out, err) @@ -646,7 +646,7 @@ class BrowserCore(RunnerCore): }); ''' % basename) - def btest(self, filename, expected=None, reference=None, force_c=False, reference_slack=0, + def btest(self, filename, expected=None, reference=None, force_c=False, reference_slack=0, manual_reference=False, post_build=None, args=[], outfile='test.html', message='.'): # TODO: use in all other tests # if we are provided the source and not a path, use that filename_is_src = '\n' in filename @@ -663,9 +663,11 @@ class BrowserCore(RunnerCore): expected = [str(i) for i in range(0, reference_slack+1)] shutil.copyfile(filepath, temp_filepath) self.reftest(path_from_root('tests', reference)) - args = args + ['--pre-js', 'reftest.js', '-s', 'GL_TESTING=1'] + if not manual_reference: + args = args + ['--pre-js', 'reftest.js', '-s', 'GL_TESTING=1'] Popen([PYTHON, EMCC, temp_filepath, '-o', outfile] + args).communicate() assert os.path.exists(outfile) + if post_build: post_build() if type(expected) is str: expected = [expected] self.run_browser(outfile, message, ['/report_result?' + e for e in expected]) diff --git a/tests/sdl_canvas_proxy.c b/tests/sdl_canvas_proxy.c new file mode 100644 index 00000000..27ef3bb4 --- /dev/null +++ b/tests/sdl_canvas_proxy.c @@ -0,0 +1,34 @@ +#include <stdio.h> +#include <stdlib.h> +#include <SDL/SDL.h> +#include <assert.h> +#include <emscripten.h> + +int main(int argc, char **argv) { + FILE *f = fopen("data.txt", "rb"); + assert(f); + assert(fgetc(f) == 'd'); + assert(fgetc(f) == 'a'); + assert(fgetc(f) == 't'); + assert(fgetc(f) == 'u'); + assert(fgetc(f) == 'm'); + fclose(f); + + SDL_Init(SDL_INIT_VIDEO); + SDL_Surface *screen = SDL_SetVideoMode(600, 450, 32, SDL_HWSURFACE); + + SDL_LockSurface(screen); + unsigned int *pixels = (unsigned int *)screen->pixels; + for (int x = 0; x < screen->w; x++) { + for (int y = 0; y < screen->h; y++) { + pixels[x + y*screen->h] = x < 300 ? (y < 200 ? 0x3377AA88 : 0xAA3377CC) : (y < 200 ? 0x0066AA77 : 0xAA006699); + } + } + SDL_UnlockSurface(screen); + + SDL_Quit(); + + EM_ASM(window.close()); + return 0; +} + diff --git a/tests/sdl_canvas_proxy.png b/tests/sdl_canvas_proxy.png Binary files differnew file mode 100644 index 00000000..cc96acfd --- /dev/null +++ b/tests/sdl_canvas_proxy.png diff --git a/tests/sdl_key_proxy.c b/tests/sdl_key_proxy.c new file mode 100644 index 00000000..bc233f29 --- /dev/null +++ b/tests/sdl_key_proxy.c @@ -0,0 +1,63 @@ +#include <stdio.h> +#include <SDL/SDL.h> +#include <SDL/SDL_ttf.h> +#include <emscripten.h> + +int result = 1; + +void one() { + SDL_Event event; + while (SDL_PollEvent(&event)) { + printf("got event %d\n", event.type); + switch(event.type) { + case SDL_KEYDOWN: + break; + case SDL_KEYUP: + // don't handle the modifier key events + if (event.key.keysym.sym == SDLK_LCTRL || + event.key.keysym.sym == SDLK_LSHIFT || + event.key.keysym.sym == SDLK_LALT) { + return; + } + if ((event.key.keysym.mod & KMOD_LCTRL) || (event.key.keysym.mod & KMOD_RCTRL)) { + result *= 2; + } + if ((event.key.keysym.mod & KMOD_LSHIFT) || (event.key.keysym.mod & KMOD_RSHIFT)) { + result *= 3; + } + if ((event.key.keysym.mod & KMOD_LALT) || (event.key.keysym.mod & KMOD_RALT)) { + result *= 5; + } + switch (event.key.keysym.sym) { + case SDLK_RIGHT: printf("right\n"); result *= 7; break; + case SDLK_LEFT: printf("left\n"); result *= 11; break; + case SDLK_DOWN: printf("down\n"); result *= 13; break; + case SDLK_UP: printf("up\n"); result *= 17; break; + case SDLK_a: printf("a\n"); result *= 19; break; + default: { + if (event.key.keysym.scancode == SDL_SCANCODE_B) { + printf("b scancode\n"); result *= 23; break; + } + printf("unknown key: sym %d scancode %d\n", event.key.keysym.sym, event.key.keysym.scancode); + REPORT_RESULT(); + emscripten_run_script("throw 'done'"); // comment this out to leave event handling active. Use the following to log DOM keys: + // addEventListener('keyup', function(event) { console.log(event.keyCode) }, true) + } + } + break; + default: /* Report an unhandled event */ + printf("I don't know what this event is!\n"); + } + } +} + +int main(int argc, char **argv) { + printf("main\n"); + SDL_Init(SDL_INIT_VIDEO); + SDL_Surface *screen = SDL_SetVideoMode(600, 450, 32, SDL_HWSURFACE); + + if (argc == 1337) one(); // keep it alive + + return 0; +} + diff --git a/tests/test_browser.py b/tests/test_browser.py index b014b4f6..e6fd6544 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -627,6 +627,28 @@ If manually bisecting: Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'sdl_canvas.c'), '-o', 'page.html', '-s', 'LEGACY_GL_EMULATION=1']).communicate() self.run_browser('page.html', '', '/report_result?1') + def test_sdl_canvas_proxy(self): + def post(): + html = open('test.html').read() + html = html.replace('</body>', ''' +<script> +function assert(x, y) { if (!x) throw 'assertion failed ' + y } + +%s + +var windowClose = window.close; +window.close = function() { + doReftest(); + setTimeout(windowClose, 1000); +}; +</script> +</body>''' % open('reftest.js').read()) + open('test.html', 'w').write(html) + + open('data.txt', 'w').write('datum') + + self.btest('sdl_canvas_proxy.c', reference='sdl_canvas_proxy.png', args=['--proxy-to-worker', '--preload-file', 'data.txt'], manual_reference=True, post_build=post) + def test_sdl_key(self): open(os.path.join(self.get_dir(), 'pre.js'), 'w').write(''' Module.postRun = function() { @@ -658,6 +680,52 @@ If manually bisecting: Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'sdl_key.c'), '-o', 'page.html', '--pre-js', 'pre.js', '-s', '''EXPORTED_FUNCTIONS=['_main', '_one']''']).communicate() self.run_browser('page.html', '', '/report_result?223092870') + def test_sdl_key_proxy(self): + open(os.path.join(self.get_dir(), 'pre.js'), 'w').write(''' + var Module = {}; + Module.postRun = function() { + function doOne() { + Module._one(); + setTimeout(doOne, 1000/60); + } + setTimeout(doOne, 1000/60); + } + ''') + + def post(): + html = open('test.html').read() + html = html.replace('</body>', ''' +<script> +function keydown(c) { + var event = document.createEvent("KeyboardEvent"); + event.initKeyEvent("keydown", true, true, window, + 0, 0, 0, 0, + c, c); + document.dispatchEvent(event); +} + +function keyup(c) { + var event = document.createEvent("KeyboardEvent"); + event.initKeyEvent("keyup", true, true, window, + 0, 0, 0, 0, + c, c); + document.dispatchEvent(event); +} + +keydown(1250);keydown(38);keyup(38);keyup(1250); // alt, up +keydown(1248);keydown(1249);keydown(40);keyup(40);keyup(1249);keyup(1248); // ctrl, shift, down +keydown(37);keyup(37); // left +keydown(39);keyup(39); // right +keydown(65);keyup(65); // a +keydown(66);keyup(66); // b +keydown(100);keyup(100); // trigger the end + +</script> +</body>''') + open('test.html', 'w').write(html) + + self.btest('sdl_key_proxy.c', '223092870', args=['--proxy-to-worker', '--pre-js', 'pre.js', '-s', '''EXPORTED_FUNCTIONS=['_main', '_one']'''], manual_reference=True, post_build=post) + def test_sdl_text(self): open(os.path.join(self.get_dir(), 'pre.js'), 'w').write(''' Module.postRun = function() { @@ -1207,6 +1275,12 @@ If manually bisecting: def test_gl_stride(self): self.btest('gl_stride.c', reference='gl_stride.png', args=['-s', 'GL_UNSAFE_OPTS=0', '-s', 'LEGACY_GL_EMULATION=1']) + def test_gl_vertex_buffer_pre(self): + self.btest('gl_vertex_buffer_pre.c', reference='gl_vertex_buffer_pre.png', args=['-s', 'GL_UNSAFE_OPTS=0', '-s', 'LEGACY_GL_EMULATION=1']) + + def test_gl_vertex_buffer(self): + self.btest('gl_vertex_buffer.c', reference='gl_vertex_buffer.png', args=['-s', 'GL_UNSAFE_OPTS=0', '-s', 'LEGACY_GL_EMULATION=1'], reference_slack=1) + def test_matrix_identity(self): self.btest('gl_matrix_identity.c', expected=['-1882984448', '460451840'], args=['-s', 'LEGACY_GL_EMULATION=1']) diff --git a/tests/test_core.py b/tests/test_core.py index 6bb5ccb6..5d4f35e8 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3742,7 +3742,8 @@ def process(filename): self.do_run(open(path_from_root('tests', 'emscripten_get_now.cpp')).read(), 'Timer resolution is good.') def test_inlinejs(self): - if Settings.ASM_JS: return self.skip('asm does not support random code, TODO: something that works in asm') + if Settings.ASM_JS: Settings.ASM_JS = 2 # skip validation, asm does not support random code + if not self.is_le32(): return self.skip('le32 needed for inline js') src = r''' #include <stdio.h> @@ -3762,7 +3763,8 @@ def process(filename): self.do_run(src, 'Inline JS is very cool\n3.64\n') def test_inlinejs2(self): - if Settings.ASM_JS: return self.skip('asm does not support random code, TODO: something that works in asm') + if Settings.ASM_JS: Settings.ASM_JS = 2 # skip validation, asm does not support random code + if not self.is_le32(): return self.skip('le32 needed for inline js') src = r''' #include <stdio.h> @@ -3773,8 +3775,8 @@ def process(filename): } void mult() { - asm("var $_$1 = Math.abs(-100); $_$1 *= 2;"); // multiline - asm __volatile__("Module.print($_$1); Module.print('\n')"); + asm("var $_$1 = Math.abs(-100); $_$1 *= 2; Module.print($_$1)"); // multiline + asm __volatile__("Module.print('done')"); } int main(int argc, char **argv) { @@ -3784,7 +3786,7 @@ def process(filename): } ''' - self.do_run(src, '4\n200\n') + self.do_run(src, '4\n200\ndone\n') def test_inlinejs3(self): if Settings.ASM_JS: return self.skip('asm does not support random code, TODO: something that works in asm') @@ -5751,8 +5753,8 @@ def process(filename): if Settings.ASM_JS and os.path.exists(SPIDERMONKEY_ENGINE[0]): out = run_js('liblib.so', engine=SPIDERMONKEY_ENGINE, full_output=True, stderr=STDOUT) - assert 'asm' in out - self.validate_asmjs(out) + if 'asm' in out: + self.validate_asmjs(out) def test_dlfcn_data_and_fptr(self): if Settings.ASM_JS: return self.skip('this is not a valid case - libraries should not be able to access their parents globals willy nilly') @@ -6226,6 +6228,139 @@ ok Settings.EXPORTED_FUNCTIONS = ['_main', '_malloc', '_free'] self.do_run(src, '''*294,153*''', force_c=True, post_build=self.dlfcn_post_build) + def test_dlfcn_longjmp(self): + if not self.can_dlfcn(): return + + self.prep_dlfcn_lib() + lib_src = r''' + #include <setjmp.h> + + void jumpy(jmp_buf buf) { + static int i = 0; + i++; + if (i == 10) longjmp(buf, i); + printf("pre %d\n", i); + } + ''' + Settings.EXPORTED_FUNCTIONS = ['_jumpy'] + dirname = self.get_dir() + filename = os.path.join(dirname, 'liblib.c') + self.build(lib_src, dirname, filename) + shutil.move(filename + '.o.js', os.path.join(dirname, 'liblib.so')) + + self.prep_dlfcn_main() + src = r''' + #include <assert.h> + #include <stdio.h> + #include <dlfcn.h> + #include <setjmp.h> + + typedef void (*jumpfunc)(jmp_buf); + + int main() { + printf("go!\n"); + + void *lib_handle; + lib_handle = dlopen("liblib.so", RTLD_NOW); + assert(lib_handle != NULL); + + jumpfunc jumpy = (jumpfunc)dlsym(lib_handle, "jumpy"); + assert(jumpy); + + jmp_buf buf; + int jmpval = setjmp(buf); + if (jmpval == 0) { + while (1) jumpy(buf); + } else { + printf("out!\n"); + } + + return 0; + } + ''' + Settings.EXPORTED_FUNCTIONS = ['_main', '_malloc', '_free'] + self.do_run(src, '''go! +pre 1 +pre 2 +pre 3 +pre 4 +pre 5 +pre 6 +pre 7 +pre 8 +pre 9 +out! +''', post_build=self.dlfcn_post_build, force_c=True) + + def zzztest_dlfcn_exceptions(self): # TODO: make this work. need to forward tempRet0 across modules + if not self.can_dlfcn(): return + + Settings.DISABLE_EXCEPTION_CATCHING = 0 + + self.prep_dlfcn_lib() + lib_src = r''' + extern "C" { + int ok() { + return 65; + } + int fail() { + throw 123; + } + } + ''' + Settings.EXPORTED_FUNCTIONS = ['_ok', '_fail'] + dirname = self.get_dir() + filename = os.path.join(dirname, 'liblib.cpp') + self.build(lib_src, dirname, filename) + shutil.move(filename + '.o.js', os.path.join(dirname, 'liblib.so')) + + self.prep_dlfcn_main() + src = r''' + #include <assert.h> + #include <stdio.h> + #include <dlfcn.h> + + typedef int (*intfunc)(); + + int main() { + printf("go!\n"); + + void *lib_handle; + lib_handle = dlopen("liblib.so", RTLD_NOW); + assert(lib_handle != NULL); + + intfunc okk = (intfunc)dlsym(lib_handle, "ok"); + intfunc faill = (intfunc)dlsym(lib_handle, "fail"); + assert(okk && faill); + + try { + printf("ok: %d\n", okk()); + } catch(...) { + printf("wha\n"); + } + + try { + printf("fail: %d\n", faill()); + } catch(int x) { + printf("int %d\n", x); + } + + try { + printf("fail: %d\n", faill()); + } catch(double x) { + printf("caught %f\n", x); + } + + return 0; + } + ''' + Settings.EXPORTED_FUNCTIONS = ['_main', '_malloc', '_free'] + self.do_run(src, '''go! +ok: 65 +int 123 +ok +''', post_build=self.dlfcn_post_build) + def test_rand(self): return self.skip('rand() is now random') # FIXME @@ -7222,7 +7357,7 @@ def process(filename): FS.registerDevice(dummy_device, {}); FS.createDataFile('/', 'file', 'abcdef', true, true); - FS.mkdev('/device', 0666, dummy_device); + FS.mkdev('/device', dummy_device); \'\'\' ) open(filename, 'w').write(src) @@ -7480,32 +7615,18 @@ def process(filename): Settings.INCLUDE_FULL_LIBRARY = 0 def test_unistd_access(self): - add_pre_run = ''' -def process(filename): - import tools.shared as shared - src = open(filename, 'r').read().replace( - '// {{PRE_RUN_ADDITIONS}}', - open(shared.path_from_root('tests', 'unistd', 'access.js'), 'r').read() - ) - open(filename, 'w').write(src) -''' + if Settings.ASM_JS: Settings.ASM_JS = 2 # skip validation, asm does not support random code + if not self.is_le32(): return self.skip('le32 needed for inline js') src = open(path_from_root('tests', 'unistd', 'access.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'access.out'), 'r').read() - self.do_run(src, expected, post_build=add_pre_run) + self.do_run(src, expected) def test_unistd_curdir(self): - add_pre_run = ''' -def process(filename): - import tools.shared as shared - src = open(filename, 'r').read().replace( - '// {{PRE_RUN_ADDITIONS}}', - open(shared.path_from_root('tests', 'unistd', 'curdir.js'), 'r').read() - ) - open(filename, 'w').write(src) -''' + if Settings.ASM_JS: Settings.ASM_JS = 2 # skip validation, asm does not support random code + if not self.is_le32(): return self.skip('le32 needed for inline js') src = open(path_from_root('tests', 'unistd', 'curdir.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'curdir.out'), 'r').read() - self.do_run(src, expected, post_build=add_pre_run) + self.do_run(src, expected) def test_unistd_close(self): src = open(path_from_root('tests', 'unistd', 'close.c'), 'r').read() @@ -7532,18 +7653,11 @@ def process(filename): self.do_run(src, expected) def test_unistd_truncate(self): - add_pre_run = ''' -def process(filename): - import tools.shared as shared - src = open(filename, 'r').read().replace( - '// {{PRE_RUN_ADDITIONS}}', - open(shared.path_from_root('tests', 'unistd', 'truncate.js'), 'r').read() - ) - open(filename, 'w').write(src) -''' + if Settings.ASM_JS: Settings.ASM_JS = 2 # skip validation, asm does not support random code + if not self.is_le32(): return self.skip('le32 needed for inline js') src = open(path_from_root('tests', 'unistd', 'truncate.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'truncate.out'), 'r').read() - self.do_run(src, expected, post_build=add_pre_run) + self.do_run(src, expected) def test_unistd_swab(self): src = open(path_from_root('tests', 'unistd', 'swab.c'), 'r').read() @@ -7569,18 +7683,11 @@ def process(filename): self.do_run(src, 'success', force_c=True) def test_unistd_links(self): - add_pre_run = ''' -def process(filename): - import tools.shared as shared - src = open(filename, 'r').read().replace( - '// {{PRE_RUN_ADDITIONS}}', - open(shared.path_from_root('tests', 'unistd', 'links.js'), 'r').read() - ) - open(filename, 'w').write(src) -''' + if Settings.ASM_JS: Settings.ASM_JS = 2 # skip validation, asm does not support random code + if not self.is_le32(): return self.skip('le32 needed for inline js') src = open(path_from_root('tests', 'unistd', 'links.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'links.out'), 'r').read() - self.do_run(src, expected, post_build=add_pre_run) + self.do_run(src, expected) def test_unistd_sleep(self): src = open(path_from_root('tests', 'unistd', 'sleep.c'), 'r').read() @@ -7588,18 +7695,12 @@ def process(filename): self.do_run(src, expected) def test_unistd_io(self): - add_pre_run = ''' -def process(filename): - import tools.shared as shared - src = open(filename, 'r').read().replace( - '// {{PRE_RUN_ADDITIONS}}', - open(shared.path_from_root('tests', 'unistd', 'io.js'), 'r').read() - ) - open(filename, 'w').write(src) -''' + if Settings.ASM_JS: Settings.ASM_JS = 2 # skip validation, asm does not support random code + if not self.is_le32(): return self.skip('le32 needed for inline js') + if self.run_name == 'o2': return self.skip('non-asm optimized builds can fail with inline js') src = open(path_from_root('tests', 'unistd', 'io.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'io.out'), 'r').read() - self.do_run(src, expected, post_build=add_pre_run) + self.do_run(src, expected) def test_unistd_misc(self): src = open(path_from_root('tests', 'unistd', 'misc.c'), 'r').read() diff --git a/tests/unistd/access.c b/tests/unistd/access.c index 89428610..4d5ba08e 100644 --- a/tests/unistd/access.c +++ b/tests/unistd/access.c @@ -1,8 +1,16 @@ #include <stdio.h> #include <errno.h> #include <unistd.h> +#include <emscripten.h> int main() { + EM_ASM( + FS.writeFile('/forbidden', ''); FS.chmod('/forbidden', 0000); + FS.writeFile('/readable', ''); FS.chmod('/readable', 0444); + FS.writeFile('/writeable', ''); FS.chmod('/writeable', 0222); + FS.writeFile('/allaccess', ''); FS.chmod('/allaccess', 0777); + ); + char* files[] = {"/readable", "/writeable", "/allaccess", "/forbidden", "/nonexistent"}; for (int i = 0; i < sizeof files / sizeof files[0]; i++) { diff --git a/tests/unistd/access.js b/tests/unistd/access.js deleted file mode 100644 index ea9e6359..00000000 --- a/tests/unistd/access.js +++ /dev/null @@ -1,4 +0,0 @@ -FS.createDataFile('/', 'forbidden', '', false, false); -FS.createDataFile('/', 'readable', '', true, false); -FS.createDataFile('/', 'writeable', '', false, true); -FS.createDataFile('/', 'allaccess', '', true, true); diff --git a/tests/unistd/access.out b/tests/unistd/access.out index dffe0b9e..d462e5a5 100644 --- a/tests/unistd/access.out +++ b/tests/unistd/access.out @@ -2,8 +2,8 @@ F_OK(/readable): 0 errno: 0 R_OK(/readable): 0 errno: 0 -X_OK(/readable): 0 -errno: 0 +X_OK(/readable): -1 +errno: 13 W_OK(/readable): -1 errno: 13 diff --git a/tests/unistd/curdir.c b/tests/unistd/curdir.c index 63b9c7fe..b9f22dd7 100644 --- a/tests/unistd/curdir.c +++ b/tests/unistd/curdir.c @@ -2,8 +2,19 @@ #include <errno.h> #include <unistd.h> #include <fcntl.h> +#include <emscripten.h> int main() { + EM_ASM( + var dummy_device = FS.makedev(64, 0); + FS.registerDevice(dummy_device, {}); + FS.mkdev('/device', dummy_device); + + FS.mkdir('/folder'); + FS.symlink('/folder', '/link'); + FS.writeFile('/file', '', { mode: 0777 }); + ); + char buffer[256]; printf("getwd: %s\n", getwd(buffer)); printf("errno: %d\n", errno); diff --git a/tests/unistd/curdir.js b/tests/unistd/curdir.js deleted file mode 100644 index 75a1d2ce..00000000 --- a/tests/unistd/curdir.js +++ /dev/null @@ -1,7 +0,0 @@ -var dummy_device = FS.makedev(64, 0); -FS.registerDevice(dummy_device, {}); - -FS.createDataFile('/', 'file', '', true, true); -FS.createFolder('/', 'folder', true, true); -FS.mkdev('/device', 0666, dummy_device); -FS.createLink('/', 'link', 'folder', true, true); diff --git a/tests/unistd/io.c b/tests/unistd/io.c index a96290ef..0ff5f4fb 100644 --- a/tests/unistd/io.c +++ b/tests/unistd/io.c @@ -3,8 +3,58 @@ #include <unistd.h> #include <fcntl.h> #include <string.h> +#include <emscripten.h> int main() { + EM_ASM( + var major = 80; + + var device = FS.makedev(major++, 0); + FS.registerDevice(device, { + open: function(stream) { + stream.payload = [65, 66, 67, 68]; + }, + read: function(stream, buffer, offset, length, pos) { + var bytesRead = 0; + for (var i = 0; i < length; i++) { + if (stream.payload.length) { + bytesRead++; + buffer[offset+i] = stream.payload.shift(); + } else { + break; + } + } + return bytesRead; + }, + write: function(stream, buffer, offset, length, pos) { + for (var i = 0; i < length; i++) { + Module.print('TO DEVICE: ' + buffer[offset+i]); + } + return i; + } + }); + FS.mkdev('/device', device); + + var broken_device = FS.makedev(major++, 0); + FS.registerDevice(broken_device, { + read: function(stream, buffer, offset, length, pos) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + }, + write: function(stream, buffer, offset, length, pos) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + }); + FS.mkdev('/broken-device', broken_device); + + // NB: These are meant to test FS.createDevice specifically, + // and as such do not use registerDevice/mkdev + FS.createDevice('/', 'createDevice-read-only', function() {}); + FS.createDevice('/', 'createDevice-write-only', null, function() {}); + + FS.mkdir('/folder', 0777); + FS.writeFile('/file', '1234567890'); + ); + char readBuffer[256] = {0}; char writeBuffer[] = "writeme"; diff --git a/tests/unistd/io.js b/tests/unistd/io.js deleted file mode 100644 index 11c0da79..00000000 --- a/tests/unistd/io.js +++ /dev/null @@ -1,52 +0,0 @@ -(function() { - var major = 80; - - var device = FS.makedev(major++, 0); - var device_ops = { - open: function(stream) { - stream.payload = [65, 66, 67, 68]; - }, - read: function(stream, buffer, offset, length, pos) { - var bytesRead = 0; - for (var i = 0; i < length; i++) { - if (stream.payload.length) { - bytesRead++; - buffer[offset+i] = stream.payload.shift(); - } else { - break; - } - } - return bytesRead; - }, - write: function(stream, buffer, offset, length, pos) { - for (var i = 0; i < length; i++) { - Module.print("TO DEVICE: " + buffer[offset+i]); - } - return i; - } - }; - FS.registerDevice(device, device_ops); - - FS.mkdev('/device', 0666, device); - - var broken_device = FS.makedev(major++, 0); - var broken_device_ops = { - read: function(stream, buffer, offset, length, pos) { - throw new FS.ErrnoError(ERRNO_CODES.EIO); - }, - write: function(stream, buffer, offset, length, pos) { - throw new FS.ErrnoError(ERRNO_CODES.EIO); - } - }; - FS.registerDevice(broken_device, broken_device_ops); - - FS.mkdev('/broken-device', 0666, broken_device); - - // NB: These are meant to test FS.createDevice specifically, - // and as such do not use registerDevice/mkdev - FS.createDevice('/', 'createDevice-read-only', function() {}); - FS.createDevice('/', 'createDevice-write-only', null, function() {}); - - FS.createDataFile('/', 'file', '1234567890', true, true); - FS.createFolder('/', 'folder', true, true); -})(); diff --git a/tests/unistd/links.c b/tests/unistd/links.c index c6da83b9..5b403c1f 100644 --- a/tests/unistd/links.c +++ b/tests/unistd/links.c @@ -1,8 +1,15 @@ #include <stdio.h> #include <errno.h> #include <unistd.h> +#include <emscripten.h> int main() { + EM_ASM( + FS.symlink('../test/../there!', '/link'); + FS.writeFile('/file', 'test'); + FS.mkdir('/folder'); + ); + char* files[] = {"/link", "/file", "/folder"}; char buffer[256] = {0}; diff --git a/tests/unistd/links.js b/tests/unistd/links.js deleted file mode 100644 index 5e58a729..00000000 --- a/tests/unistd/links.js +++ /dev/null @@ -1,3 +0,0 @@ -FS.createLink('/', 'link', '../test/../there!', true, true); -FS.createDataFile('/', 'file', 'test', true, true); -FS.createFolder('/', 'folder', true, true); diff --git a/tests/unistd/truncate.c b/tests/unistd/truncate.c index 18920976..b1d9fc96 100644 --- a/tests/unistd/truncate.c +++ b/tests/unistd/truncate.c @@ -4,8 +4,15 @@ #include <fcntl.h> #include <sys/stat.h> #include <string.h> +#include <emscripten.h> int main() { + EM_ASM( + FS.writeFile('/towrite', 'abcdef'); + FS.writeFile('/toread', 'abcdef'); + FS.chmod('/toread', 0444); + ); + struct stat s; int f = open("/towrite", O_WRONLY); int f2 = open("/toread", O_RDONLY); diff --git a/tests/unistd/truncate.js b/tests/unistd/truncate.js deleted file mode 100644 index 6a4c6868..00000000 --- a/tests/unistd/truncate.js +++ /dev/null @@ -1,2 +0,0 @@ -FS.createDataFile('/', 'towrite', 'abcdef', true, true); -FS.createDataFile('/', 'toread', 'abcdef', true, false); diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index b42164f9..c029ab4e 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -1564,6 +1564,7 @@ function normalizeAsm(func) { var data = { params: {}, // ident => ASM_* type vars: {}, // ident => ASM_* type + inlines: [], // list of inline assembly copies }; // process initial params var stats = func[3]; @@ -1621,6 +1622,10 @@ function normalizeAsm(func) { node[0] = 'name'; node[1] = 'Math_' + node[2]; } + } else if (type === 'call' && node[1][0] === 'function') { + assert(!node[1][1]); // anonymous functions only + data.inlines.push(node[1]); + node[1] = ['name', 'inlinejs']; // empty out body, leave arguments, so they are eliminated/minified properly } }); i++; @@ -1669,6 +1674,14 @@ function denormalizeAsm(func, data) { } else { stats[next] = emptyNode(); } + if (data.inlines.length > 0) { + var i = 0; + traverse(func, function(node, type) { + if (type === 'call' && node[1][0] === 'name' && node[1][1] === 'inlinejs') { + node[1] = data.inlines[i++]; // swap back in the body + } + }); + } //printErr('denormalized \n\n' + astToSrc(func) + '\n\n'); } @@ -2019,6 +2032,7 @@ function registerize(ast) { var finalAsmData = { params: {}, vars: {}, + inlines: asmData.inlines, }; for (var i = 1; i < nextReg; i++) { var reg = fullNames[i]; @@ -3018,6 +3032,9 @@ function outline(ast) { } var ignore = []; traverse(func, function(node) { + if (node[0] === 'while' && node[2][0] !== 'block') { + node[2] = ['block', [node[2]]]; // so we have a list of statements and can flatten while(1) switch + } var stats = getStatements(node); if (stats) { for (var i = 0; i < stats.length; i++) { @@ -3452,7 +3469,7 @@ function outline(ast) { }); // finalize var newFunc = ['defun', newIdent, ['sp'], code]; - var newAsmData = { params: { sp: ASM_INT }, vars: {} }; + var newAsmData = { params: { sp: ASM_INT }, vars: {}, inlines: asmData.inlines }; for (var v in codeInfo.reads) { if (v != 'sp') newAsmData.vars[v] = getAsmType(v, asmData); } @@ -3694,8 +3711,10 @@ function outline(ast) { } } } - ret.push(func); - printErr('... resulting sizes of ' + func[1] + ' is ' + ret.map(measureSize) + '\n'); + if (ret) { + ret.push(func); + printErr('... resulting sizes of ' + func[1] + ' is ' + ret.map(measureSize) + '\n'); + } } denormalizeAsm(func, asmData); }); diff --git a/tools/shared.py b/tools/shared.py index 59464d14..8031d99c 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -283,6 +283,20 @@ def check_node_version(): logging.warning('cannot check node version: %s', e) return False +# Finds the system temp directory without resorting to using the one configured in .emscripten +def find_temp_directory(): + if WINDOWS: + if os.getenv('TEMP') and os.path.isdir(os.getenv('TEMP')): + return os.getenv('TEMP') + elif os.getenv('TMP') and os.path.isdir(os.getenv('TMP')): + return os.getenv('TMP') + elif os.path.isdir('C:\\temp'): + return os.getenv('C:\\temp') + else: + return None # No luck! + else: + return '/tmp' + # Check that basic stuff we need (a JS engine to compile, Node.js, and Clang and LLVM) # exists. # The test runner always does this check (through |force|). emcc does this less frequently, @@ -429,7 +443,6 @@ EMCC = path_from_root('emcc') EMXX = path_from_root('em++') EMAR = path_from_root('emar') EMRANLIB = path_from_root('emranlib') -EMLIBTOOL = path_from_root('emlibtool') EMCONFIG = path_from_root('em-config') EMLINK = path_from_root('emlink.py') EMMAKEN = path_from_root('tools', 'emmaken.py') @@ -451,8 +464,13 @@ class Configuration: try: self.TEMP_DIR = TEMP_DIR except NameError: - logging.debug('TEMP_DIR not defined in ~/.emscripten, using /tmp') - self.TEMP_DIR = '/tmp' + self.TEMP_DIR = find_temp_directory() + if self.TEMP_DIR == None: + logging.critical('TEMP_DIR not defined in ' + os.path.expanduser('~\\.emscripten') + ", and could not detect a suitable directory! Please configure .emscripten to contain a variable TEMP_DIR='/path/to/temp/dir'.") + logging.debug('TEMP_DIR not defined in ~/.emscripten, using ' + self.TEMP_DIR) + + if not os.path.isdir(self.TEMP_DIR): + logging.critical("The temp directory TEMP_DIR='" + self.TEMP_DIR + "' doesn't seem to exist! Please make sure that the path is correct.") self.CANONICAL_TEMP_DIR = os.path.join(self.TEMP_DIR, 'emscripten_temp') @@ -470,12 +488,13 @@ class Configuration: save_debug_files=os.environ.get('EMCC_DEBUG_SAVE')) def apply_configuration(): - global configuration, DEBUG, EMSCRIPTEN_TEMP_DIR, DEBUG_CACHE, CANONICAL_TEMP_DIR + global configuration, DEBUG, EMSCRIPTEN_TEMP_DIR, DEBUG_CACHE, CANONICAL_TEMP_DIR, TEMP_DIR configuration = Configuration() DEBUG = configuration.DEBUG EMSCRIPTEN_TEMP_DIR = configuration.EMSCRIPTEN_TEMP_DIR DEBUG_CACHE = configuration.DEBUG_CACHE CANONICAL_TEMP_DIR = configuration.CANONICAL_TEMP_DIR + TEMP_DIR = configuration.TEMP_DIR apply_configuration() logging.basicConfig(format='%(levelname)-8s %(name)s: %(message)s') @@ -527,15 +546,13 @@ def get_llvm_target(): return os.environ.get('EMCC_LLVM_TARGET') or 'le32-unknown-nacl' # 'i386-pc-linux-gnu' LLVM_TARGET = get_llvm_target() +# COMPILER_OPTS: options passed to clang when generating bitcode for us try: COMPILER_OPTS # Can be set in EM_CONFIG, optionally except: COMPILER_OPTS = [] -# Force a simple, standard target as much as possible: target 32-bit linux, and disable various flags that hint at other platforms -COMPILER_OPTS = COMPILER_OPTS + ['-m32', '-U__i386__', '-U__i386', '-Ui386', - '-U__SSE__', '-U__SSE_MATH__', '-U__SSE2__', '-U__SSE2_MATH__', '-U__MMX__', - '-DEMSCRIPTEN', '-D__EMSCRIPTEN__', '-U__STRICT_ANSI__', - '-D__IEEE_LITTLE_ENDIAN', '-fno-math-errno', +COMPILER_OPTS = COMPILER_OPTS + ['-m32', '-DEMSCRIPTEN', '-D__EMSCRIPTEN__', + '-fno-math-errno', #'-fno-threadsafe-statics', # disabled due to issue 1289 '-target', LLVM_TARGET] @@ -543,6 +560,11 @@ if LLVM_TARGET == 'le32-unknown-nacl': COMPILER_OPTS = filter(lambda opt: opt != '-m32', COMPILER_OPTS) # le32 target is 32-bit anyhow, no need for -m32 COMPILER_OPTS += ['-U__native_client__', '-U__pnacl__', '-U__ELF__'] # The nacl target is originally used for Google Native Client. Emscripten is not NaCl, so remove the platform #define, when using their triple. +# Remove various platform specific defines, and set little endian +COMPILER_STANDARDIZATION_OPTS = ['-U__i386__', '-U__i386', '-Ui386', '-U__STRICT_ANSI__', '-D__IEEE_LITTLE_ENDIAN', + '-U__SSE__', '-U__SSE_MATH__', '-U__SSE2__', '-U__SSE2_MATH__', '-U__MMX__', + '-U__APPLE__', '-U__linux__'] + USE_EMSDK = not os.environ.get('EMMAKEN_NO_SDK') if USE_EMSDK: @@ -559,9 +581,8 @@ if USE_EMSDK: '-Xclang', '-isystem' + path_from_root('system', 'include', 'gfx'), '-Xclang', '-isystem' + path_from_root('system', 'include', 'net'), '-Xclang', '-isystem' + path_from_root('system', 'include', 'SDL'), - ] + [ - '-U__APPLE__', '-U__linux__' ] + EMSDK_OPTS += COMPILER_STANDARDIZATION_OPTS if LLVM_TARGET != 'le32-unknown-nacl': EMSDK_CXX_OPTS = ['-nostdinc++'] # le32 target does not need -nostdinc++ else: @@ -570,6 +591,7 @@ if USE_EMSDK: else: EMSDK_OPTS = [] EMSDK_CXX_OPTS = [] + COMPILER_OPTS += COMPILER_STANDARDIZATION_OPTS #print >> sys.stderr, 'SDK opts', ' '.join(EMSDK_OPTS) #print >> sys.stderr, 'Compiler opts', ' '.join(COMPILER_OPTS) @@ -786,7 +808,6 @@ class Building: env['LD'] = EMCC if not WINDOWS else 'python %r' % EMCC env['LDSHARED'] = EMCC if not WINDOWS else 'python %r' % EMCC env['RANLIB'] = EMRANLIB if not WINDOWS else 'python %r' % EMRANLIB - #env['LIBTOOL'] = EMLIBTOOL if not WINDOWS else 'python %r' % EMLIBTOOL env['EMMAKEN_COMPILER'] = Building.COMPILER env['EMSCRIPTEN_TOOLS'] = path_from_root('tools') env['CFLAGS'] = env['EMMAKEN_CFLAGS'] = ' '.join(Building.COMPILER_TEST_OPTS) diff --git a/tools/test-js-optimizer-asm-outline1-output.js b/tools/test-js-optimizer-asm-outline1-output.js index 612da16a..27f93d8a 100644 --- a/tools/test-js-optimizer-asm-outline1-output.js +++ b/tools/test-js-optimizer-asm-outline1-output.js @@ -372,6 +372,57 @@ function switchh() { } STACKTOP = sp; } +function switchh2() { + var helper$0 = 0, helper$1 = 0, sp = 0; + sp = STACKTOP; + STACKTOP = STACKTOP + 280 | 0; + while (1) { + helper$0 = 1; + helper$1 = x; + if (helper$0) { + helper$0 = 0; + switch (helper$1 | 0) { + case 0: + f(0); + g(); + break; + default: + { + helper$0 = 1; + } + } + } + HEAP32[sp + 8 >> 2] = helper$0; + HEAP32[sp + 16 >> 2] = helper$1; + HEAP32[sp + 40 >> 2] = 0; + HEAP32[sp + 44 >> 2] = 0; + switchh2$2(sp); + helper$0 = HEAP32[sp + 8 >> 2] | 0; + tempValue = HEAP32[sp + 40 >> 2] | 0; + tempInt = HEAP32[sp + 44 >> 2] | 0; + tempDouble = +HEAPF32[sp + 44 >> 2]; + HEAP32[sp + 40 >> 2] = 0; + HEAP32[sp + 44 >> 2] = 0; + if ((tempValue | 0) == 5) { + STACKTOP = sp; + return; + } + HEAP32[sp + 8 >> 2] = helper$0; + HEAP32[sp + 16 >> 2] = helper$1; + HEAP32[sp + 32 >> 2] = 0; + HEAP32[sp + 36 >> 2] = 0; + switchh2$1(sp); + helper$0 = HEAP32[sp + 8 >> 2] | 0; + if (helper$0) { + helper$0 = 0; + HEAP32[sp + 16 >> 2] = helper$1; + HEAP32[sp + 24 >> 2] = 0; + HEAP32[sp + 28 >> 2] = 0; + switchh2$0(sp); + } + } + STACKTOP = sp; +} function lin$0(sp) { sp = sp | 0; c(14); @@ -793,4 +844,86 @@ function switchh$2(sp) { } while (0); HEAP32[sp + 8 >> 2] = helper$0; } +function switchh2$0(sp) { + sp = sp | 0; + var helper$1 = 0; + helper$1 = HEAP32[sp + 16 >> 2] | 0; + switch (helper$1 | 0) { + case 4: + f(4); + g(); + case 5: + f(5); + g(); + case 6: + f(6); + g(); + default: + print(9); + } +} +function switchh2$1(sp) { + sp = sp | 0; + var helper$0 = 0, helper$1 = 0; + helper$0 = HEAP32[sp + 8 >> 2] | 0; + helper$1 = HEAP32[sp + 16 >> 2] | 0; + if (helper$0) { + helper$0 = 0; + switch (helper$1 | 0) { + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: + case 3: + f(3); + g(); + break; + default: + { + helper$0 = 1; + } + } + } + HEAP32[sp + 8 >> 2] = helper$0; +} +function switchh2$2(sp) { + sp = sp | 0; + var helper$0 = 0, helper$1 = 0; + helper$0 = HEAP32[sp + 8 >> 2] | 0; + helper$1 = HEAP32[sp + 16 >> 2] | 0; + OL : do { + if (helper$0) { + helper$0 = 0; + switch (helper$1 | 0) { + case 1: + f(1); + g(); + return; + default: + { + helper$0 = 1; + } + } + } + if (helper$0) { + helper$0 = 0; + switch (helper$1 | 0) { + case 2: + f(2); + g(); + break; + default: + { + helper$0 = 1; + } + } + } + } while (0); + HEAP32[sp + 8 >> 2] = helper$0; +} diff --git a/tools/test-js-optimizer-asm-outline1.js b/tools/test-js-optimizer-asm-outline1.js index 4282ec8e..b7ec9011 100644 --- a/tools/test-js-optimizer-asm-outline1.js +++ b/tools/test-js-optimizer-asm-outline1.js @@ -307,5 +307,45 @@ function switchh() { } } } +function switchh2() { + while (1) switch (x) { + case 0: + f(0); + g(); + break; + case 1: + f(1); + g(); + return; + case 2: + f(2); + g(); + break; + case 21: // gotta keem em unseparated + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: + case 3: // these too + f(3); + g(); + break; + case 4: + f(4); + g(); + case 5: + f(5); + g(); + case 6: + f(6); + g(); + default: + print(9); + } +} // EMSCRIPTEN_GENERATED_FUNCTIONS // EXTRA_INFO: { "sizeToOutline": 30, "allowCostlyOutlines": 1 } |