diff options
34 files changed, 1343 insertions, 521 deletions
@@ -86,3 +86,5 @@ a license to everyone to use it as detailed in LICENSE.) * David Barksdale <david.barksdale@adcedosolutions.com> * Manfred Manik Nerurkar <nerurkar*at*made-apps.biz> (copyright owned by MADE, GmbH) * Joseph Gentle <me@josephg.com> +* Douglas T. Crosher <dtc-moz@scieneer.com> (copyright owned by Mozilla Founcation) +* Soeren Balko <soeren.balko@gmail.com> @@ -522,14 +522,14 @@ if CONFIGURE_CONFIG or CMAKE_CONFIG: open(tempout, 'w').write('//\n') src = None - for i in range(len(sys.argv)): - if sys.argv[i].endswith('.c'): + for arg in sys.argv: + if arg.endswith('.c'): try: - src = open(sys.argv[i]).read() - if debug_configure: open(tempout, 'a').write('============= ' + sys.argv[i] + '\n' + src + '\n=============\n\n') + src = open(arg).read() + if debug_configure: open(tempout, 'a').write('============= ' + arg + '\n' + src + '\n=============\n\n') except: pass - if sys.argv[i].endswith('.s'): + if arg.endswith('.s'): if debug_configure: open(tempout, 'a').write('(compiling .s assembly, must use clang\n') use_js = 0 @@ -826,7 +826,7 @@ try: newargs[i] = '' newargs[i+1] = '' elif newargs[i].startswith('--use-preload-cache'): - use_preload_cache = True; + use_preload_cache = True newargs[i] = '' elif newargs[i] == '--ignore-dynamic-linking': ignore_dynamic_linking = True @@ -952,7 +952,7 @@ try: if not prefix: continue if l.startswith(prefix): l = l[len(prefix):] - break; + break libs.append(l) newargs[i] = '' else: @@ -1121,9 +1121,8 @@ try: # Optimize source files if llvm_opts > 0: - for i in range(len(input_files)): - input_file = input_files[i] - if input_files[i].endswith(SOURCE_SUFFIXES): + for i, input_file in enumerate(input_files): + if input_file.endswith(SOURCE_SUFFIXES): temp_file = temp_files[i] logging.debug('optimizing %s with -O%d' % (input_file, llvm_opts)) shared.Building.llvm_opt(temp_file, llvm_opts) @@ -1214,7 +1213,7 @@ try: os.path.join('libc', 'gen', 'vwarn.c'), os.path.join('libc', 'gen', 'vwarnx.c'), os.path.join('libc', 'stdlib', 'strtod.c'), - ]; + ] return build_libc('libc.bc', libc_files) def apply_libc(need): diff --git a/emscripten.py b/emscripten.py index be54b035..9a2abb9a 100755 --- a/emscripten.py +++ b/emscripten.py @@ -168,10 +168,10 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, if DEBUG_CACHE and not out: dfpath = os.path.join(get_configuration().TEMP_DIR, "ems_" + shortkey) dfp = open(dfpath, 'w') - dfp.write(pre_input); - dfp.write("\n\n========================== settings_text\n\n"); - dfp.write(settings_text); - dfp.write("\n\n========================== libraries\n\n"); + dfp.write(pre_input) + dfp.write("\n\n========================== settings_text\n\n") + dfp.write(settings_text) + dfp.write("\n\n========================== libraries\n\n") dfp.write("\n".join(libraries)) dfp.close() print >>sys.stderr, ' cache miss, key data dumped to %s' % dfpath @@ -490,7 +490,7 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, global_vars = map(lambda g: g['name'], filter(lambda g: settings['NAMED_GLOBALS'] or g.get('external') or g.get('unIndexable'), forwarded_json['Variables']['globals'].values())) global_funcs = ['_' + key for key, value in forwarded_json['Functions']['libraryFunctions'].iteritems() if value != 2] def math_fix(g): - return g if not g.startswith('Math_') else g.split('_')[1]; + return g if not g.startswith('Math_') else g.split('_')[1] asm_global_funcs = ''.join([' var ' + g.replace('.', '_') + '=global.' + g + ';\n' for g in maths]) + \ ''.join([' var ' + g + '=env.' + math_fix(g) + ';\n' for g in basic_funcs + global_funcs]) asm_global_vars = ''.join([' var ' + g + '=env.' + g + '|0;\n' for g in basic_vars + global_vars]) + \ diff --git a/src/jsifier.js b/src/jsifier.js index 86931f3e..82b78d0a 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -1479,8 +1479,8 @@ function JSify(data, functionsOnly, givenFunctions) { return inline.apply(null, args); // Warning: inlining does not prevent recalculation of the arguments. They should be simple identifiers } - if (ASM_JS) { - // remove unneeded arguments, which the asm sig can show us. this lets us alias memset with llvm.memset, we just + if (ASM_JS && ident.indexOf('llvm_') >= 0) { + // remove unneeded arguments in llvm intrinsic functions, which the asm sig can show us. this lets us alias memset with llvm.memset, we just // drop the final 2 args so things validate properly in asm var libsig = LibraryManager.library[simpleIdent + '__sig']; if (libsig) { diff --git a/src/library.js b/src/library.js index bf969fd7..24e35178 100644 --- a/src/library.js +++ b/src/library.js @@ -588,8 +588,11 @@ LibraryManager.library = { // Create the I/O devices. var devFolder = FS.createFolder('/', 'dev', true, true); var stdin = FS.createDevice(devFolder, 'stdin', input); + stdin.isTerminal = !stdinOverridden; var stdout = FS.createDevice(devFolder, 'stdout', null, output); + stdout.isTerminal = !stdoutOverridden; var stderr = FS.createDevice(devFolder, 'stderr', null, error); + stderr.isTerminal = !stderrOverridden; FS.createDevice(devFolder, 'tty', input, output); FS.createDevice(devFolder, 'null', function(){}, function(){}); @@ -601,7 +604,6 @@ LibraryManager.library = { isRead: true, isWrite: false, isAppend: false, - isTerminal: !stdinOverridden, error: false, eof: false, ungotten: [] @@ -613,7 +615,6 @@ LibraryManager.library = { isRead: false, isWrite: true, isAppend: false, - isTerminal: !stdoutOverridden, error: false, eof: false, ungotten: [] @@ -625,7 +626,6 @@ LibraryManager.library = { isRead: false, isWrite: true, isAppend: false, - isTerminal: !stderrOverridden, error: false, eof: false, ungotten: [] @@ -737,7 +737,8 @@ LibraryManager.library = { // int closedir(DIR *dirp); // http://pubs.opengroup.org/onlinepubs/007908799/xsh/closedir.html if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) { - return ___setErrNo(ERRNO_CODES.EBADF); + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } else { _free(FS.streams[dirp].currentEntry); FS.streams[dirp] = null; @@ -749,7 +750,8 @@ LibraryManager.library = { // long int telldir(DIR *dirp); // http://pubs.opengroup.org/onlinepubs/007908799/xsh/telldir.html if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) { - return ___setErrNo(ERRNO_CODES.EBADF); + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } else { return FS.streams[dirp].position; } @@ -865,10 +867,6 @@ LibraryManager.library = { } var file = FS.findObject(Pointer_stringify(path)); if (file === null) return -1; - if (!file.write) { - ___setErrNo(ERRNO_CODES.EPERM); - return -1; - } file.timestamp = time; return 0; }, @@ -1213,7 +1211,7 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.EEXIST); return -1; } - if ((isWrite || isCreate || isTruncate) && target.isFolder) { + if ((isWrite || isTruncate) && target.isFolder) { ___setErrNo(ERRNO_CODES.EISDIR); return -1; } @@ -1381,7 +1379,7 @@ LibraryManager.library = { posix_fallocate: function(fd, offset, len) { // int posix_fallocate(int fd, off_t offset, off_t len); // http://pubs.opengroup.org/onlinepubs/009695399/functions/posix_fallocate.html - if (!FS.streams[fd] || FS.streams[fd].link || + if (!FS.streams[fd] || !FS.streams[fd].isWrite || FS.streams[fd].link || FS.streams[fd].isFolder || FS.streams[fd].isDevice) { ___setErrNo(ERRNO_CODES.EBADF); return -1; @@ -1688,13 +1686,16 @@ LibraryManager.library = { isatty: function(fildes) { // int isatty(int fildes); // http://pubs.opengroup.org/onlinepubs/000095399/functions/isatty.html - if (!FS.streams[fildes]) { + var stream = FS.streams[fildes]; + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return 0; } - if (FS.streams[fildes].isTerminal) return 1; - ___setErrNo(ERRNO_CODES.ENOTTY); - return 0; + if (!stream.object.isTerminal) { + ___setErrNo(ERRNO_CODES.ENOTTY); + return 0; + } + return 1; }, lchown__deps: ['chown'], lchown: function(path, owner, group) { @@ -1916,30 +1917,21 @@ LibraryManager.library = { if (!_ttyname.ret) _ttyname.ret = _malloc(256); return _ttyname_r(fildes, _ttyname.ret, 256) ? 0 : _ttyname.ret; }, - ttyname_r__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], + ttyname_r__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'isatty'], ttyname_r: function(fildes, name, namesize) { // int ttyname_r(int fildes, char *name, size_t namesize); // http://pubs.opengroup.org/onlinepubs/000095399/functions/ttyname.html var stream = FS.streams[fildes]; + var ttyname = '/dev/tty'; if (!stream) { return ___setErrNo(ERRNO_CODES.EBADF); - } else { - var object = stream.object; - if (!object.isDevice || !object.input || !object.output) { - return ___setErrNo(ERRNO_CODES.ENOTTY); - } else { - var ret = stream.path; - if (namesize < ret.length + 1) { - return ___setErrNo(ERRNO_CODES.ERANGE); - } else { - for (var i = 0; i < ret.length; i++) { - {{{ makeSetValue('name', 'i', 'ret.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('name', 'i', '0', 'i8') }}} - return 0; - } - } + } else if (!_isatty(fildes)) { + return ___setErrNo(ERRNO_CODES.ENOTTY); + } else if (namesize < ttyname.length + 1) { + return ___setErrNo(ERRNO_CODES.ERANGE); } + writeStringToMemory(ttyname, name); + return 0; }, symlink__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], symlink: function(path1, path2) { @@ -6223,10 +6215,282 @@ LibraryManager.library = { }, strftime_l: 'strftime', // no locale support yet + strptime__deps: ['__tm_struct_layout'], strptime: function(buf, format, tm) { // char *strptime(const char *restrict buf, const char *restrict format, struct tm *restrict tm); // http://pubs.opengroup.org/onlinepubs/009695399/functions/strptime.html - // TODO: Implement. + var pattern = Pointer_stringify(format); + + // escape special characters + // TODO: not sure we really need to escape all of these in JS regexps + var SPECIAL_CHARS = '\\!@#$^&*()+=-[]/{}|:<>?,.'; + for (var i=0, ii=SPECIAL_CHARS.length; i<ii; ++i) { + pattern = pattern.replace(new RegExp('\\'+SPECIAL_CHARS[i], 'g'), '\\'+SPECIAL_CHARS[i]); + } + + // reduce number of matchers + var EQUIVALENT_MATCHERS = { + '%A': '%a', + '%B': '%b', + '%c': '%x\\s+%X', + '%D': '%m\\/%d\\/%y', + '%e': '%d', + '%h': '%b', + '%R': '%H\\:%M', + '%r': '%I\\:%M\\:%S\\s%p', + '%T': '%H\\:%M\\:%S', + '%x': '%m\\/%d\\/(?:%y|%Y)', + '%X': '%H\\:%M\\:%S' + }; + for (var matcher in EQUIVALENT_MATCHERS) { + pattern = pattern.replace(matcher, EQUIVALENT_MATCHERS[matcher]); + } + + // TODO: take care of locale + + var DATE_PATTERNS = { + /* weeday name */ '%a': '(?:Sun(?:day)?)|(?:Mon(?:day)?)|(?:Tue(?:sday)?)|(?:Wed(?:nesday)?)|(?:Thu(?:rsday)?)|(?:Fri(?:day)?)|(?:Sat(?:urday)?)', + /* month name */ '%b': '(?:Jan(?:uary)?)|(?:Feb(?:ruary)?)|(?:Mar(?:ch)?)|(?:Apr(?:il)?)|May|(?:Jun(?:e)?)|(?:Jul(?:y)?)|(?:Aug(?:ust)?)|(?:Sep(?:tember)?)|(?:Oct(?:ober)?)|(?:Nov(?:ember)?)|(?:Dec(?:ember)?)', + /* century */ '%C': '\\d\\d', + /* day of month */ '%d': '0[1-9]|[1-9](?!\\d)|1\\d|2\\d|30|31', + /* hour (24hr) */ '%H': '\\d(?!\\d)|[0,1]\\d|20|21|22|23', + /* hour (12hr) */ '%I': '\\d(?!\\d)|0\\d|10|11|12', + /* day of year */ '%j': '00[1-9]|0?[1-9](?!\\d)|0?[1-9]\\d(?!\\d)|[1,2]\\d\\d|3[0-6]\\d', + /* month */ '%m': '0[1-9]|[1-9](?!\\d)|10|11|12', + /* minutes */ '%M': '0\\d|\\d(?!\\d)|[1-5]\\d', + /* whitespace */ '%n': '\\s', + /* AM/PM */ '%p': 'AM|am|PM|pm|A\\.M\\.|a\\.m\\.|P\\.M\\.|p\\.m\\.', + /* seconds */ '%S': '0\\d|\\d(?!\\d)|[1-5]\\d|60', + /* week number */ '%U': '0\\d|\\d(?!\\d)|[1-4]\\d|50|51|52|53', + /* week number */ '%W': '0\\d|\\d(?!\\d)|[1-4]\\d|50|51|52|53', + /* weekday number */ '%w': '[0-6]', + /* 2-digit year */ '%y': '\\d\\d', + /* 4-digit year */ '%Y': '\\d\\d\\d\\d', + /* % */ '%%': '%', + /* whitespace */ '%t': '\\s', + }; + + var MONTH_NUMBERS = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11}; + var MONTH_DAYS_REGULAR = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + var MONTH_DAYS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + var DAY_NUMBERS_SUN_FIRST = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6}; + var DAY_NUMBERS_MON_FIRST = {MON: 0, TUE: 1, WED: 2, THU: 3, FRI: 4, SAT: 5, SUN: 6}; + + var isLeapYear = function(year) { + return year%4===0 && (year%100!==0 || year%400===0); + }; + + var arraySum = function(array, index) { + var sum = 0; + for (var i=0; i<=index; sum += array[i++]); + return sum; + }; + + var addDays = function(date, days) { + while(days>0) { + var leap = isLeapYear(date.getFullYear()); + var currentMonth = date.getMonth(); + var daysInCurrentMonth = (leap ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR)[currentMonth]; + + if (days>daysInCurrentMonth-date.getDate()) { + // we spill over to next month + days -= (daysInCurrentMonth-date.getDate()+1); + date.setDate(1); + if (currentMonth<11) { + date.setMonth(currentMonth+1) + } else { + date.setMonth(0); + date.setFullYear(date.getFullYear()+1); + } + } else { + // we stay in current month + date.setDate(date.getDate()+days); + return date; + } + } + return date; + }; + + for (var datePattern in DATE_PATTERNS) { + pattern = pattern.replace(datePattern, '('+datePattern+DATE_PATTERNS[datePattern]+')'); + } + + // take care of capturing groups + var capture = []; + for (var i=pattern.indexOf('%'); i>=0; i=pattern.indexOf('%')) { + capture.push(pattern[i+1]); + pattern = pattern.replace(new RegExp('\\%'+pattern[i+1], 'g'), ''); + } + + var matches = new RegExp('^'+pattern).exec(Pointer_stringify(buf)) + // Module['print'](Pointer_stringify(buf)+ ' is matched by '+((new RegExp('^'+pattern)).source)+' into: '+JSON.stringify(matches)); + + var initDate = function() { + var fixup = function(value, min, max) { + return (typeof value !== 'number' || isNaN(value)) ? min : (value>=min ? (value<=max ? value: max): min); + }; + + return { + year: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_year', 'i32') }}} + 1900 , 1970, 9999), + month: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_mon', 'i32') }}}, 0, 11), + day: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_mday', 'i32') }}}, 1, 31), + hour: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_hour', 'i32') }}}, 0, 23), + min: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_min', 'i32') }}}, 0, 59), + sec: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_sec', 'i32') }}}, 0, 59) + }; + }; + + if (matches) { + var date = initDate(); + var value; + + var getMatch = function(symbol) { + var pos = capture.indexOf(symbol); + // check if symbol appears in regexp + if (pos >= 0) { + // return matched value or null (falsy!) for non-matches + return matches[pos+1]; + } + return; + } + + // seconds + if ((value=getMatch('S'))) { + date.sec = parseInt(value); + } + + // minutes + if ((value=getMatch('M'))) { + date.min = parseInt(value); + } + + // hours + if ((value=getMatch('H'))) { + // 24h clock + date.hour = parseInt(value); + } else if ((value = getMatch('I'))) { + // AM/PM clock + var hour = parseInt(value); + if ((value=getMatch('p'))) { + hour += value.toUpperCase()[0] === 'P' ? 12 : 0; + } + date.hour = hour; + } + + // year + if ((value=getMatch('Y'))) { + // parse from four-digit year + date.year = parseInt(value); + } else if ((value=getMatch('y'))) { + // parse from two-digit year... + var year = parseInt(value); + if ((value=getMatch('C'))) { + // ...and century + year += parseInt(value)*100; + } else { + // ...and rule-of-thumb + year += year<69 ? 2000 : 1900; + } + date.year = year; + } + + // month + if ((value=getMatch('m'))) { + // parse from month number + date.month = parseInt(value)-1; + } else if ((value=getMatch('b'))) { + // parse from month name + date.month = MONTH_NUMBERS[value.substring(0,3).toUpperCase()] || 0; + // TODO: derive month from day in year+year, week number+day of week+year + } + + // day + if ((value=getMatch('d'))) { + // get day of month directly + date.day = parseInt(value); + } else if ((value=getMatch('j'))) { + // get day of month from day of year ... + var day = parseInt(value); + var leapYear = isLeapYear(date.year); + for (var month=0; month<12; ++month) { + var daysUntilMonth = arraySum(leapYear ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR, month-1); + if (day<=daysUntilMonth+(leapYear ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR)[month]) { + date.day = day-daysUntilMonth; + } + } + } else if ((value=getMatch('a'))) { + // get day of month from weekday ... + var weekDay = value.substring(0,3).toUpperCase(); + if ((value=getMatch('U'))) { + // ... and week number (Sunday being first day of week) + // Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. + // All days in a new year preceding the first Sunday are considered to be in week 0. + var weekDayNumber = DAY_NUMBERS_SUN_FIRST[weekDay]; + var weekNumber = parseInt(value); + + // January 1st + var janFirst = new Date(date.year, 0, 1); + var endDate; + if (janFirst.getDay() === 0) { + // Jan 1st is a Sunday, and, hence in the 1st CW + endDate = addDays(janFirst, weekDayNumber+7*(weekNumber-1)); + } else { + // Jan 1st is not a Sunday, and, hence still in the 0th CW + endDate = addDays(janFirst, 7-janFirst.getDay()+weekDayNumber+7*(weekNumber-1)); + } + date.day = endDate.getDate(); + date.month = endDate.getMonth(); + } else if ((value=getMatch('W'))) { + // ... and week number (Monday being first day of week) + // Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. + // All days in a new year preceding the first Monday are considered to be in week 0. + var weekDayNumber = DAY_NUMBERS_MON_FIRST[weekDay]; + var weekNumber = parseInt(value); + + // January 1st + var janFirst = new Date(date.year, 0, 1); + var endDate; + if (janFirst.getDay()===1) { + // Jan 1st is a Monday, and, hence in the 1st CW + endDate = addDays(janFirst, weekDayNumber+7*(weekNumber-1)); + } else { + // Jan 1st is not a Monday, and, hence still in the 0th CW + endDate = addDays(janFirst, 7-janFirst.getDay()+1+weekDayNumber+7*(weekNumber-1)); + } + + date.day = endDate.getDate(); + date.month = endDate.getMonth(); + } + } + + /* + tm_sec int seconds after the minute 0-61* + tm_min int minutes after the hour 0-59 + tm_hour int hours since midnight 0-23 + tm_mday int day of the month 1-31 + tm_mon int months since January 0-11 + tm_year int years since 1900 + tm_wday int days since Sunday 0-6 + tm_yday int days since January 1 0-365 + tm_isdst int Daylight Saving Time flag + */ + + var fullDate = new Date(date.year, date.month, date.day, date.hour, date.min, date.sec, 0); + {{{ makeSetValue('tm', '___tm_struct_layout.tm_sec', 'fullDate.getSeconds()', 'i32') }}} + {{{ makeSetValue('tm', '___tm_struct_layout.tm_min', 'fullDate.getMinutes()', 'i32') }}} + {{{ makeSetValue('tm', '___tm_struct_layout.tm_hour', 'fullDate.getHours()', 'i32') }}} + {{{ makeSetValue('tm', '___tm_struct_layout.tm_mday', 'fullDate.getDate()', 'i32') }}} + {{{ makeSetValue('tm', '___tm_struct_layout.tm_mon', 'fullDate.getMonth()', 'i32') }}} + {{{ makeSetValue('tm', '___tm_struct_layout.tm_year', 'fullDate.getFullYear()-1900', 'i32') }}} + {{{ makeSetValue('tm', '___tm_struct_layout.tm_wday', 'fullDate.getDay()', 'i32') }}} + {{{ makeSetValue('tm', '___tm_struct_layout.tm_yday', 'arraySum(isLeapYear(fullDate.getFullYear()) ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR, fullDate.getMonth()-1)+fullDate.getDate()-1', 'i32') }}} + {{{ makeSetValue('tm', '___tm_struct_layout.tm_isdst', '0', 'i32') }}} + + // we need to convert the matched sequence into an integer array to take care of UTF-8 characters > 0x7F + // TODO: not sure that intArrayFromString handles all unicode characters correctly + return buf+intArrayFromString(matches[0]).length-1; + } + return 0; }, strptime_l: 'strptime', // no locale support yet diff --git a/src/library_browser.js b/src/library_browser.js index d9fd3ee5..7f79b2bd 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -84,13 +84,17 @@ mergeInto(LibraryManager.library, { var imagePlugin = {}; imagePlugin['canHandle'] = function(name) { - return !Module.noImageDecoding && /\.(jpg|jpeg|png|bmp)$/.exec(name); + return !Module.noImageDecoding && /\.(jpg|jpeg|png|bmp)$/i.test(name); }; imagePlugin['handle'] = function(byteArray, name, onload, onerror) { var b = null; if (Browser.hasBlobConstructor) { try { b = new Blob([byteArray], { type: getMimetype(name) }); + if (b.size !== byteArray.length) { // Safari bug #118630 + // Safari's Blob can only take an ArrayBuffer + b = new Blob([(new Uint8Array(byteArray)).buffer], { type: getMimetype(name) }); + } } catch(e) { Runtime.warnOnce('Blob constructor present but fails: ' + e + '; falling back to blob builder'); } diff --git a/src/library_gl.js b/src/library_gl.js index e59492cf..959773bc 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -122,14 +122,6 @@ var LibraryGL = { } }, - // Linear lookup in one of the tables (buffers, programs, etc.). TODO: consider using a weakmap to make this faster, if it matters - scan: function(table, object) { - for (var item in table) { - if (table[item] == object) return item; - } - return 0; - }, - // Find a token in a shader source string findToken: function(source, token) { function isIdentChar(ch) { @@ -402,15 +394,15 @@ var LibraryGL = { {{{ makeSetValue('p', 'i*4', 'result[i]', 'i32') }}}; } } else if (result instanceof WebGLBuffer) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.buffers, result)', 'i32') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; } else if (result instanceof WebGLProgram) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.programs, result)', 'i32') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; } else if (result instanceof WebGLFramebuffer) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.framebuffers, result)', 'i32') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; } else if (result instanceof WebGLRenderbuffer) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.renderbuffers, result)', 'i32') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; } else if (result instanceof WebGLTexture) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.textures, result)', 'i32') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; } else { throw 'Unknown object returned from WebGL getParameter'; } @@ -445,15 +437,15 @@ var LibraryGL = { {{{ makeSetValue('p', 'i*4', 'result[i]', 'float') }}}; } } else if (result instanceof WebGLBuffer) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.buffers, result)', 'float') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; } else if (result instanceof WebGLProgram) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.programs, result)', 'float') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; } else if (result instanceof WebGLFramebuffer) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.framebuffers, result)', 'float') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; } else if (result instanceof WebGLRenderbuffer) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.renderbuffers, result)', 'float') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; } else if (result instanceof WebGLTexture) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.textures, result)', 'float') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; } else { throw 'Unknown object returned from WebGL getParameter'; } @@ -508,7 +500,9 @@ var LibraryGL = { glGenTextures: function(n, textures) { for (var i = 0; i < n; i++) { var id = GL.getNewId(GL.textures); - GL.textures[id] = Module.ctx.createTexture(); + var texture = Module.ctx.createTexture(); + texture.name = id; + GL.textures[id] = texture; {{{ makeSetValue('textures', 'i*4', 'id', 'i32') }}}; } }, @@ -517,7 +511,9 @@ var LibraryGL = { glDeleteTextures: function(n, textures) { for (var i = 0; i < n; i++) { var id = {{{ makeGetValue('textures', 'i*4', 'i32') }}}; - Module.ctx.deleteTexture(GL.textures[id]); + var texture = GL.textures[id]; + Module.ctx.deleteTexture(texture); + texture.name = 0; GL.textures[id] = null; } }, @@ -622,7 +618,9 @@ var LibraryGL = { glGenBuffers: function(n, buffers) { for (var i = 0; i < n; i++) { var id = GL.getNewId(GL.buffers); - GL.buffers[id] = Module.ctx.createBuffer(); + var buffer = Module.ctx.createBuffer(); + buffer.name = id; + GL.buffers[id] = buffer; {{{ makeSetValue('buffers', 'i*4', 'id', 'i32') }}}; } }, @@ -631,7 +629,9 @@ var LibraryGL = { glDeleteBuffers: function(n, buffers) { for (var i = 0; i < n; i++) { var id = {{{ makeGetValue('buffers', 'i*4', 'i32') }}}; - Module.ctx.deleteBuffer(GL.buffers[id]); + var buffer = GL.buffers[id]; + Module.ctx.deleteBuffer(buffer); + buffer.name = 0; GL.buffers[id] = null; if (id == GL.currArrayBuffer) GL.currArrayBuffer = 0; @@ -665,7 +665,9 @@ var LibraryGL = { glGenRenderbuffers: function(n, renderbuffers) { for (var i = 0; i < n; i++) { var id = GL.getNewId(GL.renderbuffers); - GL.renderbuffers[id] = Module.ctx.createRenderbuffer(); + var renderbuffer = Module.ctx.createRenderbuffer(); + renderbuffer.name = id; + GL.renderbuffers[id] = renderbuffer; {{{ makeSetValue('renderbuffers', 'i*4', 'id', 'i32') }}}; } }, @@ -674,8 +676,10 @@ var LibraryGL = { glDeleteRenderbuffers: function(n, renderbuffers) { for (var i = 0; i < n; i++) { var id = {{{ makeGetValue('renderbuffers', 'i*4', 'i32') }}}; - Module.ctx.deleteRenderbuffer(GL.renderbuffers[id]); - GL.renderbuffers[id]; + var renderbuffer = GL.renderbuffers[id]; + Module.ctx.deleteRenderbuffer(renderbuffer); + renderbuffer.name = 0; + GL.renderbuffers[id] = null; } }, @@ -1139,13 +1143,17 @@ var LibraryGL = { glCreateProgram__sig: 'i', glCreateProgram: function() { var id = GL.getNewId(GL.programs); - GL.programs[id] = Module.ctx.createProgram(); + var program = Module.ctx.createProgram(); + program.name = id; + GL.programs[id] = program; return id; }, glDeleteProgram__sig: 'vi', glDeleteProgram: function(program) { - Module.ctx.deleteProgram(GL.programs[program]); + var program = GL.programs[program]; + Module.ctx.deleteProgram(program); + program.name = 0; GL.programs[program] = null; GL.uniformTable[program] = null; }, @@ -1221,7 +1229,9 @@ var LibraryGL = { glGenFramebuffers: function(n, ids) { for (var i = 0; i < n; ++i) { var id = GL.getNewId(GL.framebuffers); - GL.framebuffers[id] = Module.ctx.createFramebuffer(); + var framebuffer = Module.ctx.createFramebuffer(); + framebuffer.name = id; + GL.framebuffers[id] = framebuffer; {{{ makeSetValue('ids', 'i*4', 'id', 'i32') }}}; } }, @@ -1230,7 +1240,9 @@ var LibraryGL = { glDeleteFramebuffers: function(n, framebuffers) { for (var i = 0; i < n; ++i) { var id = {{{ makeGetValue('framebuffers', 'i*4', 'i32') }}}; - Module.ctx.deleteFramebuffer(GL.framebuffers[id]); + var framebuffer = GL.framebuffers[id]; + Module.ctx.deleteFramebuffer(framebuffer); + framebuffer.name = 0; GL.framebuffers[id] = null; } }, diff --git a/src/library_sdl.js b/src/library_sdl.js index 176a2fae..b7d73862 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -21,6 +21,8 @@ var LibrarySDL = { version: null, surfaces: {}, + // A pool of freed canvas elements. Reusing them avoids GC pauses. + canvasPool: [], events: [], fonts: [null], @@ -246,7 +248,7 @@ var LibrarySDL = { }, translateRGBAToCSSRGBA: function(r, g, b, a) { - return 'rgba(' + r + ',' + g + ',' + b + ',' + (a/255) + ')'; + return 'rgba(' + (r&0xff) + ',' + (g&0xff) + ',' + (b&0xff) + ',' + (a&0xff)/255 + ')'; }, translateRGBAToColor: function(r, g, b, a) { @@ -288,7 +290,11 @@ var LibrarySDL = { SDL.GL = SDL.GL || useWebGL; var canvas; if (!usePageCanvas) { - canvas = document.createElement('canvas'); + if (SDL.canvasPool.length > 0) { + canvas = SDL.canvasPool.pop(); + } else { + canvas = document.createElement('canvas'); + } canvas.width = width; canvas.height = height; } else { @@ -355,14 +361,56 @@ var LibrarySDL = { }, freeSurface: function(surf) { - _free(SDL.surfaces[surf].buffer); - _free(SDL.surfaces[surf].pixelFormat); + var info = SDL.surfaces[surf]; + if (!info.usePageCanvas && info.canvas) SDL.canvasPool.push(info.canvas); + _free(info.buffer); + _free(info.pixelFormat); _free(surf); SDL.surfaces[surf] = null; }, + touchX:0, touchY: 0, + receiveEvent: function(event) { switch(event.type) { + case 'touchstart': + event.preventDefault(); + var touch = event.touches[0]; + touchX = touch.pageX; + touchY = touch.pageY; + var event = { + type: 'mousedown', + button: 0, + pageX: touchX, + pageY: touchY + }; + SDL.DOMButtons[0] = 1; + SDL.events.push(event); + break; + case 'touchmove': + event.preventDefault(); + var touch = event.touches[0]; + touchX = touch.pageX; + touchY = touch.pageY; + event = { + type: 'mousemove', + button: 0, + pageX: touchX, + pageY: touchY + }; + SDL.events.push(event); + break; + case 'touchend': + event.preventDefault(); + event = { + type: 'mouseup', + button: 0, + pageX: touchX, + pageY: touchY + }; + SDL.DOMButtons[0] = 0; + SDL.events.push(event); + break; case 'mousemove': if (Browser.pointerLock) { // workaround for firefox bug 750111 @@ -1022,6 +1070,7 @@ var LibrarySDL = { surfData.ctx.fillStyle = SDL.translateColorToCSSRGBA(color); surfData.ctx.fillRect(r.x, r.y, r.w, r.h); surfData.ctx.restore(); + return 0; }, SDL_BlitSurface__deps: ['SDL_UpperBlit'], @@ -1031,11 +1080,19 @@ var LibrarySDL = { zoomSurface: function(src, x, y, smooth) { var srcData = SDL.surfaces[src]; - var w = srcData.width*x; - var h = srcData.height*y; - var ret = SDL.makeSurface(w, h, srcData.flags, false, 'zoomSurface'); + var w = srcData.width * x; + var h = srcData.height * y; + var ret = SDL.makeSurface(Math.abs(w), Math.abs(h), srcData.flags, false, 'zoomSurface'); var dstData = SDL.surfaces[ret]; - dstData.ctx.drawImage(srcData.canvas, 0, 0, w, h); + if (x >= 0 && y >= 0) dstData.ctx.drawImage(srcData.canvas, 0, 0, w, h); + else { + dstData.ctx.save(); + dstData.ctx.scale(x < 0 ? -1 : 1, y < 0 ? -1 : 1); + dstData.ctx.drawImage(srcData.canvas, w < 0 ? w : 0, h < 0 ? h : 0, Math.abs(w), Math.abs(h)); + // XXX I think this should work according to the spec, but currently + // fails on FF: dstData.ctx.drawImage(srcData.canvas, 0, 0, w, h); + dstData.ctx.restore(); + } return ret; }, @@ -1056,6 +1113,14 @@ var LibrarySDL = { SDL.surfaces[surf].alpha = alpha; }, + SDL_SetColorKey: function(surf, flag, key) { + // SetColorKey assigns one color to be rendered as transparent. I don't + // think the canvas API allows for anything like this, and iterating through + // each pixel to replace that color seems prohibitively expensive. + Runtime.warnOnce('SDL_SetColorKey is a no-op for performance reasons'); + return 0; + }, + SDL_GetTicks: function() { return Math.floor(Date.now() - SDL.startTime); }, @@ -1472,15 +1537,20 @@ var LibrarySDL = { }, Mix_HaltChannel: function(channel) { - var info = SDL.channels[channel]; - if (info.audio) { - info.audio.pause(); - info.audio = null; - } else { - Module.printErr('No Audio for channel: ' + channel); + function halt(channel) { + var info = SDL.channels[channel]; + if (info.audio) { + info.audio.pause(); + info.audio = null; + } + if (SDL.channelFinished) { + Runtime.getFuncWrapper(SDL.channelFinished, 'vi')(channel); + } } - if (SDL.channelFinished) { - Runtime.getFuncWrapper(SDL.channelFinished, 'vi')(channel); + if (channel != -1) { + halt(channel); + } else { + for (var i = 0; i < SDL.channels.length; ++i) halt(i); } return 0; }, @@ -1667,6 +1737,7 @@ var LibrarySDL = { }, TTF_RenderText_Blended: 'TTF_RenderText_Solid', // XXX ignore blending vs. solid TTF_RenderText_Shaded: 'TTF_RenderText_Solid', // XXX ignore blending vs. solid + TTF_RenderUTF8_Solid: 'TTF_RenderText_Solid', TTF_SizeText: function(font, text, w, h) { var fontData = SDL.fonts[font]; @@ -1691,35 +1762,114 @@ var LibrarySDL = { // SDL gfx + $SDL_gfx: { + drawRectangle: function(surf, x1, y1, x2, y2, action, cssColor) { + x1 = x1 << 16 >> 16; + y1 = y1 << 16 >> 16; + x2 = x2 << 16 >> 16; + y2 = y2 << 16 >> 16; + var surfData = SDL.surfaces[surf]; + assert(!surfData.locked); // but we could unlock and re-lock if we must.. + // TODO: if ctx does not change, leave as is, and also do not re-set xStyle etc. + var x = x1 < x2 ? x1 : x2; + var y = y1 < y2 ? y1 : y2; + var w = Math.abs(x2 - x1); + var h = Math.abs(y2 - y1); + surfData.ctx.save(); + surfData.ctx[action + 'Style'] = cssColor; + surfData.ctx[action + 'Rect'](x, y, w, h); + surfData.ctx.restore(); + }, + drawLine: function(surf, x1, y1, x2, y2, cssColor) { + x1 = x1 << 16 >> 16; + y1 = y1 << 16 >> 16; + x2 = x2 << 16 >> 16; + y2 = y2 << 16 >> 16; + var surfData = SDL.surfaces[surf]; + assert(!surfData.locked); // but we could unlock and re-lock if we must.. + surfData.ctx.save(); + surfData.ctx.strokeStyle = cssColor; + surfData.ctx.beginPath(); + surfData.ctx.moveTo(x1, y1); + surfData.ctx.lineTo(x2, y2); + surfData.ctx.stroke(); + surfData.ctx.restore(); + }, + // See http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + drawEllipse: function(surf, x, y, rx, ry, action, cssColor) { + x = x << 16 >> 16; + y = y << 16 >> 16; + rx = rx << 16 >> 16; + ry = ry << 16 >> 16; + var surfData = SDL.surfaces[surf]; + assert(!surfData.locked); // but we could unlock and re-lock if we must.. + + surfData.ctx.save(); + surfData.ctx.beginPath(); + surfData.ctx.translate(x, y); + surfData.ctx.scale(rx, ry); + surfData.ctx.arc(0, 0, 1, 0, 2 * Math.PI); + surfData.ctx.restore(); + + surfData.ctx.save(); + surfData.ctx[action + 'Style'] = cssColor; + surfData.ctx[action](); + surfData.ctx.restore(); + }, + // the gfx library uses something different from the rest of SDL... + translateColorToCSSRGBA: function(rgba) { + return 'rgba(' + (rgba>>>24) + ',' + (rgba>>16 & 0xff) + ',' + (rgba>>8 & 0xff) + ',' + (rgba&0xff) + ')'; + } + }, + + boxColor__deps: ['$SDL_gfx'], + boxColor: function(surf, x1, y1, x2, y2, color) { + return SDL_gfx.drawRectangle(surf, x1, y1, x2, y2, 'fill', SDL_gfx.translateColorToCSSRGBA(color)); + }, + + boxRGBA__deps: ['$SDL_gfx'], boxRGBA: function(surf, x1, y1, x2, y2, r, g, b, a) { - var surfData = SDL.surfaces[surf]; - assert(!surfData.locked); // but we could unlock and re-lock if we must.. - // TODO: if ctx does not change, leave as is, and also do not re-set xStyle etc. - surfData.ctx.save(); - surfData.ctx.fillStyle = SDL.translateRGBAToCSSRGBA(r, g, b, a); - surfData.ctx.fillRect(x1, y1, x2-x1, y2-y1); - surfData.ctx.restore(); + return SDL_gfx.drawRectangle(surf, x1, y1, x2, y2, 'fill', SDL.translateRGBAToCSSRGBA(r, g, b, a)); + }, + + rectangleColor__deps: ['$SDL_gfx'], + rectangleColor: function(surf, x1, y1, x2, y2, color) { + return SDL_gfx.drawRectangle(surf, x1, y1, x2, y2, 'stroke', SDL_gfx.translateColorToCSSRGBA(color)); }, + rectangleRGBA__deps: ['$SDL_gfx'], rectangleRGBA: function(surf, x1, y1, x2, y2, r, g, b, a) { - var surfData = SDL.surfaces[surf]; - assert(!surfData.locked); // but we could unlock and re-lock if we must.. - surfData.ctx.save(); - surfData.ctx.strokeStyle = SDL.translateRGBAToCSSRGBA(r, g, b, a); - surfData.ctx.strokeRect(x1, y1, x2-x1, y2-y1); - surfData.ctx.restore(); + return SDL_gfx.drawRectangle(surf, x1, y1, x2, y2, 'stroke', SDL.translateRGBAToCSSRGBA(r, g, b, a)); + }, + + ellipseColor__deps: ['$SDL_gfx'], + ellipseColor: function(surf, x, y, rx, ry, color) { + return SDL_gfx.drawEllipse(surf, x, y, rx, ry, 'stroke', SDL_gfx.translateColorToCSSRGBA(color)); + }, + + ellipseRGBA__deps: ['$SDL_gfx'], + ellipseRGBA: function(surf, x, y, rx, ry, r, g, b, a) { + return SDL_gfx.drawEllipse(surf, x, y, rx, ry, 'stroke', SDL.translateRGBAToCSSRGBA(r, g, b, a)); + }, + + filledEllipseColor__deps: ['$SDL_gfx'], + filledEllipseColor: function(surf, x, y, rx, ry, color) { + return SDL_gfx.drawEllipse(surf, x, y, rx, ry, 'fill', SDL_gfx.translateColorToCSSRGBA(color)); }, + filledEllipseRGBA__deps: ['$SDL_gfx'], + filledEllipseRGBA: function(surf, x, y, rx, ry, r, g, b, a) { + return SDL_gfx.drawEllipse(surf, x, y, rx, ry, 'fill', SDL.translateRGBAToCSSRGBA(r, g, b, a)); + }, + + lineColor__deps: ['$SDL_gfx'], + lineColor: function(surf, x1, y1, x2, y2, color) { + return SDL_gfx.drawLine(surf, x1, y1, x2, y2, SDL_gfx.translateColorToCSSRGBA(color)); + }, + + lineRGBA__deps: ['$SDL_gfx'], lineRGBA: function(surf, x1, y1, x2, y2, r, g, b, a) { - var surfData = SDL.surfaces[surf]; - assert(!surfData.locked); // but we could unlock and re-lock if we must.. - surfData.ctx.save(); - surfData.ctx.strokeStyle = SDL.translateRGBAToCSSRGBA(r, g, b, a); - surfData.ctx.beginPath(); - surfData.ctx.moveTo(x1, y1); - surfData.ctx.lineTo(x2, y2); - surfData.ctx.stroke(); - surfData.ctx.restore(); + return SDL_gfx.drawLine(surf, x1, y1, x2, y2, SDL.translateRGBAToCSSRGBA(r, g, b, a)); }, pixelRGBA__deps: ['boxRGBA'], @@ -1805,12 +1955,18 @@ var LibrarySDL = { return -1; }, + // Joysticks + + SDL_NumJoysticks: function() { return 0 }, + + SDL_JoystickOpen: function(deviceIndex) { return 0 }, + + SDL_JoystickGetButton: function(joystick, button) { return 0 }, + // Misc SDL_InitSubSystem: function(flags) { return 0 }, - SDL_NumJoysticks: function() { return 0 }, - SDL_RWFromFile: function(filename, mode) { return filename; // XXX We just forward the filename }, diff --git a/src/parseTools.js b/src/parseTools.js index dfd4b7ed..eb200c65 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -2222,9 +2222,9 @@ function processMathop(item) { // basic integer ops case 'add': return handleOverflow(getFastValue(idents[0], '+', idents[1], item.type), bits); case 'sub': return handleOverflow(getFastValue(idents[0], '-', idents[1], item.type), bits); - case 'sdiv': case 'udiv': return makeRounding(getFastValue(idents[0], '/', idents[1], item.type), bits, op[0] === 's'); + case 'sdiv': case 'udiv': return makeRounding(getFastValue(idents[0], '/', idents[1], item.type), bits, true); case 'mul': return getFastValue(idents[0], '*', idents[1], item.type); // overflow handling is already done in getFastValue for '*' - case 'urem': case 'srem': return makeRounding(getFastValue(idents[0], '%', idents[1], item.type), bits, op[0] === 's'); + case 'urem': case 'srem': return makeRounding(getFastValue(idents[0], '%', idents[1], item.type), bits, true); case 'or': { if (bits > 32) { assert(bits === 64, 'Too many bits for or: ' + bits); diff --git a/system/include/SDL/SDL_keycode.h b/system/include/SDL/SDL_keycode.h index 472ca28e..84063b8b 100644 --- a/system/include/SDL/SDL_keycode.h +++ b/system/include/SDL/SDL_keycode.h @@ -310,7 +310,8 @@ enum SDLK_KBDILLUMDOWN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KBDILLUMDOWN), SDLK_KBDILLUMUP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KBDILLUMUP), SDLK_EJECT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_EJECT), - SDLK_SLEEP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SLEEP) + SDLK_SLEEP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SLEEP), + SDLK_LAST = SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES) }; /** diff --git a/system/include/libc/sys/dirent.h b/system/include/libc/sys/dirent.h index 0d8b02b5..e6ce831e 100644 --- a/system/include/libc/sys/dirent.h +++ b/system/include/libc/sys/dirent.h @@ -24,6 +24,7 @@ DIR *opendir(const char *); void seekdir(DIR *, long); long telldir(DIR *); DIR *readdir(DIR *); +int readdir_r(DIR *, struct dirent *, struct dirent **); int closedir(DIR *dirp); void rewinddir(DIR *dirp); int scandir(const char *dirp, diff --git a/tests/cases/zeroextarg.ll b/tests/cases/zeroextarg.ll new file mode 100644 index 00000000..25efb7ec --- /dev/null +++ b/tests/cases/zeroextarg.ll @@ -0,0 +1,22 @@ +; ModuleID = 'tests/hello_world.bc' +target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128" +target triple = "i386-pc-linux-gnu" + +@.str = private unnamed_addr constant [15 x i8] c"hello, world!\0A\00", align 1 ; [#uses=1 type=[15 x i8]*] + +define void @glSampleCoverage(float %18, i8 zeroext %invert) { +entry: + %call = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([15 x i8]* @.str, i32 0, i32 0)) ; [#uses=0 type=i32] + ret void +} + +; [#uses=0] +define i32 @main() { +entry: + tail call void @glSampleCoverage(float 3.5, i8 zeroext 12) + ret i32 1 +} + +; [#uses=1] +declare i32 @printf(i8*, ...) + diff --git a/tests/dirent/test_readdir.c b/tests/dirent/test_readdir.c new file mode 100644 index 00000000..9f7b12e8 --- /dev/null +++ b/tests/dirent/test_readdir.c @@ -0,0 +1,132 @@ +#include <assert.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> + +static void create_file(const char *path, const char *buffer, int mode) { + int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, mode); + assert(fd >= 0); + + int err = write(fd, buffer, sizeof(char) * strlen(buffer)); + assert(err == (sizeof(char) * strlen(buffer))); + + close(fd); +} + +void setup() { + mkdir("nocanread", 0111); + mkdir("foobar", 0777); + create_file("foobar/file.txt", "ride into the danger zone", 0666); +} + +void cleanup() { + rmdir("nocanread"); + unlink("foobar/file.txt"); + rmdir("foobar"); +} + +void test() { + int err; + int loc; + DIR *dir; + struct dirent *ent; + struct dirent ent_r; + struct dirent *result; + + // check bad opendir input + dir = opendir("noexist"); + assert(!dir); + assert(errno == ENOENT); + dir = opendir("nocanread"); + assert(!dir); + assert(errno == EACCES); + dir = opendir("foobar/file.txt"); + assert(!dir); + assert(errno == ENOTDIR); + + // check bad readdir input + dir = opendir("foobar"); + closedir(dir); + ent = readdir(dir); + assert(!ent); + assert(errno == EBADF); + + // check bad readdir_r input + dir = opendir("foobar"); + closedir(dir); + err = readdir_r(dir, NULL, &result); + assert(err == EBADF); + + // + // do a normal read with readdir + // + dir = opendir("foobar"); + assert(dir); + ent = readdir(dir); + assert(!strcmp(ent->d_name, ".")); + assert(ent->d_type & DT_DIR); + ent = readdir(dir); + assert(!strcmp(ent->d_name, "..")); + assert(ent->d_type & DT_DIR); + ent = readdir(dir); + assert(!strcmp(ent->d_name, "file.txt")); + assert(ent->d_type & DT_REG); + ent = readdir(dir); + assert(!ent); + + // test rewinddir + rewinddir(dir); + ent = readdir(dir); + assert(!strcmp(ent->d_name, ".")); + + // test seek / tell + rewinddir(dir); + ent = readdir(dir); + assert(!strcmp(ent->d_name, ".")); + loc = telldir(dir); + ent = readdir(dir); + assert(!strcmp(ent->d_name, "..")); + ent = readdir(dir); + assert(!strcmp(ent->d_name, "file.txt")); + seekdir(dir, loc); + ent = readdir(dir); + assert(!strcmp(ent->d_name, "..")); + + // + // do a normal read with readdir_r + // + rewinddir(dir); + err = readdir_r(dir, &ent_r, &result); + assert(!err); + assert(&ent_r == result); + assert(!strcmp(ent_r.d_name, ".")); + err = readdir_r(dir, &ent_r, &result); + assert(!err); + assert(&ent_r == result); + assert(!strcmp(ent_r.d_name, "..")); + err = readdir_r(dir, &ent_r, &result); + assert(!err); + assert(&ent_r == result); + assert(!strcmp(ent_r.d_name, "file.txt")); + err = readdir_r(dir, &ent_r, &result); + assert(!err); + assert(!result); + + err = closedir(dir); + assert(!err); + + puts("success"); +} + +int main() { + atexit(cleanup); + signal(SIGABRT, cleanup); + setup(); + test(); + return EXIT_SUCCESS; +}
\ No newline at end of file diff --git a/tests/fcntl-misc/src.c b/tests/fcntl-misc/src.c index 73734969..7cdbbcd6 100644 --- a/tests/fcntl-misc/src.c +++ b/tests/fcntl-misc/src.c @@ -6,7 +6,7 @@ int main() { struct stat s; - int f = open("/test", O_RDONLY, 0777); + int f = open("/test", O_RDWR, 0777); printf("posix_fadvise: %d\n", posix_fadvise(f, 3, 2, POSIX_FADV_DONTNEED)); printf("errno: %d\n", errno); diff --git a/tests/fcntl-open/output.txt b/tests/fcntl-open/output.txt index 314ae880..07b106ac 100644 --- a/tests/fcntl-open/output.txt +++ b/tests/fcntl-open/output.txt @@ -19,8 +19,8 @@ errno: 0 st_mode: 0100000 EXISTING FOLDER 0,1 -success: 0 -errno: 21 +success: 1 +errno: 0 st_mode: 040000 NON-EXISTING 0,1 @@ -139,8 +139,8 @@ errno: 0 st_mode: 0100000 EXISTING FOLDER 0,9 -success: 0 -errno: 21 +success: 1 +errno: 0 st_mode: 040000 NON-EXISTING 0,9 @@ -720,4 +720,4 @@ st_mode: 0100000 CREAT success: 1 -errno: 0 +errno: 0
\ No newline at end of file diff --git a/tests/fcntl-open/src.c b/tests/fcntl-open/src.c index 52d2e7e4..bd52dd3f 100644 --- a/tests/fcntl-open/src.c +++ b/tests/fcntl-open/src.c @@ -1,13 +1,45 @@ -#include <stdio.h> +#include <assert.h> #include <errno.h> -#include <sys/stat.h> #include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> +#include <unistd.h> +#include <sys/stat.h> -int main() { +char nonexistent_name[] = "noexist-##"; + +void create_file(const char *path, const char *buffer, int mode) { + int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, mode); + assert(fd >= 0); + + int err = write(fd, buffer, sizeof(char) * strlen(buffer)); + assert(err == (sizeof(char) * strlen(buffer))); + + close(fd); +} + +void setup() { + create_file("test-file", "abcdef", 0777); + mkdir("test-folder", 0777); +} + +void cleanup() { + unlink("test-file"); + rmdir("test-folder"); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 16; j++) { + nonexistent_name[8] = 'a' + i; + nonexistent_name[9] = 'a' + j; + unlink(nonexistent_name); + } + } + unlink("creat-me"); +} + +void test() { struct stat s; int modes[] = {O_RDONLY, O_WRONLY, O_RDWR}; - char nonexistent_name[] = "/noexist-##"; for (int i = 0; i < 3; i++) { for (int j = 0; j < 16; j++) { @@ -18,25 +50,25 @@ int main() { if (j & 0x8) flags |= O_APPEND; printf("EXISTING FILE %d,%d\n", i, j); - printf("success: %d\n", open("/test-file", flags, 0777) != -1); + printf("success: %d\n", open("test-file", flags, 0777) != -1); printf("errno: %d\n", errno); - stat("/test-file", &s); + stat("test-file", &s); printf("st_mode: 0%o\n", s.st_mode & 037777777000); memset(&s, 0, sizeof s); printf("\n"); errno = 0; printf("EXISTING FOLDER %d,%d\n", i, j); - printf("success: %d\n", open("/test-folder", flags, 0777) != -1); + printf("success: %d\n", open("test-folder", flags, 0777) != -1); printf("errno: %d\n", errno); - stat("/test-folder", &s); + stat("test-folder", &s); printf("st_mode: 0%o\n", s.st_mode & 037777777000); memset(&s, 0, sizeof s); printf("\n"); errno = 0; - nonexistent_name[9] = 'a' + i; - nonexistent_name[10] = 'a' + j; + nonexistent_name[8] = 'a' + i; + nonexistent_name[9] = 'a' + j; printf("NON-EXISTING %d,%d\n", i, j); printf("success: %d\n", open(nonexistent_name, flags, 0777) != -1); printf("errno: %d\n", errno); @@ -49,8 +81,14 @@ int main() { } printf("CREAT\n"); - printf("success: %d\n", creat("/creat-me", 0777) != -1); + printf("success: %d\n", creat("creat-me", 0777) != -1); printf("errno: %d\n", errno); - - return 0; } + +int main() { + atexit(cleanup); + signal(SIGABRT, cleanup); + setup(); + test(); + return EXIT_SUCCESS; +}
\ No newline at end of file diff --git a/tests/runner.py b/tests/runner.py index 4bd4b806..1f6b39f4 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -4958,6 +4958,130 @@ The current type of b is: 9 ''' self.do_run(src, 'time: ') # compilation check, mainly + + def test_strptime_tm(self): + src=r''' + #include <time.h> + #include <stdio.h> + #include <string.h> + + int main() { + struct tm tm; + char *ptr = strptime("17410105012000", "%H%M%S%d%m%Y", &tm); + + printf("%s: %s, %d/%d/%d %d:%d:%d", + (ptr != NULL && *ptr=='\0') ? "OK" : "ERR", + tm.tm_wday == 0 ? "Sun" : (tm.tm_wday == 1 ? "Mon" : (tm.tm_wday == 2 ? "Tue" : (tm.tm_wday == 3 ? "Wed" : (tm.tm_wday == 4 ? "Thu" : (tm.tm_wday == 5 ? "Fri" : (tm.tm_wday == 6 ? "Sat" : "ERR")))))), + tm.tm_mon+1, + tm.tm_mday, + tm.tm_year+1900, + tm.tm_hour, + tm.tm_min, + tm.tm_sec + ); + } + ''' + self.do_run(src, 'OK: Wed, 1/5/2000 17:41:1') + + def test_strptime_days(self): + src = r''' + #include <time.h> + #include <stdio.h> + #include <string.h> + + static const struct { + const char *input; + const char *format; + } day_tests[] = { + { "2000-01-01", "%Y-%m-%d"}, + { "03/03/00", "%D"}, + { "9/9/99", "%x"}, + { "19990502123412", "%Y%m%d%H%M%S"}, + { "2001 20 Mon", "%Y %U %a"}, + { "2006 4 Fri", "%Y %U %a"}, + { "2001 21 Mon", "%Y %W %a"}, + { "2013 29 Wed", "%Y %W %a"}, + { "2000-01-01 08:12:21 AM", "%Y-%m-%d %I:%M:%S %p"}, + { "2000-01-01 08:12:21 PM", "%Y-%m-%d %I:%M:%S %p"}, + { "2001 17 Tue", "%Y %U %a"}, + { "2001 8 Thursday", "%Y %W %a"}, + }; + + int main() { + struct tm tm; + + for (int i = 0; i < sizeof (day_tests) / sizeof (day_tests[0]); ++i) { + memset (&tm, '\0', sizeof (tm)); + char *ptr = strptime(day_tests[i].input, day_tests[i].format, &tm); + + printf("%s: %d/%d/%d (%dth DoW, %dth DoY)\n", (ptr != NULL && *ptr=='\0') ? "OK" : "ERR", tm.tm_mon+1, tm.tm_mday, 1900+tm.tm_year, tm.tm_wday, tm.tm_yday); + } + } + ''' + self.do_run(src, 'OK: 1/1/2000 (6th DoW, 0th DoY)\n'\ + 'OK: 3/3/2000 (5th DoW, 62th DoY)\n'\ + 'OK: 9/9/1999 (4th DoW, 251th DoY)\n'\ + 'OK: 5/2/1999 (0th DoW, 121th DoY)\n'\ + 'OK: 5/21/2001 (1th DoW, 140th DoY)\n'\ + 'OK: 1/27/2006 (5th DoW, 26th DoY)\n'\ + 'OK: 5/21/2001 (1th DoW, 140th DoY)\n'\ + 'OK: 7/24/2013 (3th DoW, 204th DoY)\n'\ + 'OK: 1/1/2000 (6th DoW, 0th DoY)\n'\ + 'OK: 1/1/2000 (6th DoW, 0th DoY)\n'\ + 'OK: 5/1/2001 (2th DoW, 120th DoY)\n'\ + 'OK: 2/22/2001 (4th DoW, 52th DoY)\n'\ + ) + + def test_strptime_reentrant(self): + src=r''' + #include <time.h> + #include <stdio.h> + #include <string.h> + #include <stdlib.h> + + int main () { + int result = 0; + struct tm tm; + + memset (&tm, 0xaa, sizeof (tm)); + + /* Test we don't crash on uninitialized struct tm. + Some fields might contain bogus values until everything + needed is initialized, but we shouldn't crash. */ + if (strptime ("2007", "%Y", &tm) == NULL + || strptime ("12", "%d", &tm) == NULL + || strptime ("Feb", "%b", &tm) == NULL + || strptime ("13", "%M", &tm) == NULL + || strptime ("21", "%S", &tm) == NULL + || strptime ("16", "%H", &tm) == NULL) { + printf("ERR: returned NULL"); + exit(EXIT_FAILURE); + } + + if (tm.tm_sec != 21 || tm.tm_min != 13 || tm.tm_hour != 16 + || tm.tm_mday != 12 || tm.tm_mon != 1 || tm.tm_year != 107 + || tm.tm_wday != 1 || tm.tm_yday != 42) { + printf("ERR: unexpected tm content (1) - %d/%d/%d %d:%d:%d", tm.tm_mon+1, tm.tm_mday, tm.tm_year+1900, tm.tm_hour, tm.tm_min, tm.tm_sec); + exit(EXIT_FAILURE); + } + + if (strptime ("8", "%d", &tm) == NULL) { + printf("ERR: strptime failed"); + exit(EXIT_FAILURE); + } + + if (tm.tm_sec != 21 || tm.tm_min != 13 || tm.tm_hour != 16 + || tm.tm_mday != 8 || tm.tm_mon != 1 || tm.tm_year != 107 + || tm.tm_wday != 4 || tm.tm_yday != 38) { + printf("ERR: unexpected tm content (2) - %d/%d/%d %d:%d:%d", tm.tm_mon+1, tm.tm_mday, tm.tm_year+1900, tm.tm_hour, tm.tm_min, tm.tm_sec); + exit(EXIT_FAILURE); + } + + printf("OK"); + } + ''' + self.do_run(src, 'OK') + def test_intentional_fault(self): # Some programs intentionally segfault themselves, we should compile that into a throw src = r''' @@ -6794,84 +6918,9 @@ def process(filename): self.emcc_args += ['--embed-file', 'three_numbers.txt'] self.do_run(src, 'match = 3\nx = -1.0, y = 0.1, z = -0.1\n') - def test_folders(self): - add_pre_run = ''' -def process(filename): - src = open(filename, 'r').read().replace( - '// {{PRE_RUN_ADDITIONS}}', - \'\'\' - FS.createFolder('/', 'test', true, false); - FS.createPath('/', 'test/hello/world/', true, false); - FS.createPath('/test', 'goodbye/world/', true, false); - FS.createPath('/test/goodbye', 'noentry', false, false); - FS.createDataFile('/test', 'freeforall.ext', 'abc', true, true); - FS.createDataFile('/test', 'restricted.ext', 'def', false, false); - \'\'\' - ) - open(filename, 'w').write(src) -''' - src = r''' - #include <stdio.h> - #include <dirent.h> - #include <errno.h> - - int main() { - struct dirent *e; - - // Basic correct behaviour. - DIR* d = opendir("/test"); - printf("--E: %d\n", errno); - while ((e = readdir(d))) puts(e->d_name); - printf("--E: %d\n", errno); - - // Empty folder; tell/seek. - puts("****"); - d = opendir("/test/hello/world/"); - e = readdir(d); - puts(e->d_name); - int pos = telldir(d); - e = readdir(d); - puts(e->d_name); - seekdir(d, pos); - e = readdir(d); - puts(e->d_name); - - // Errors. - puts("****"); - printf("--E: %d\n", errno); - d = opendir("/test/goodbye/noentry"); - printf("--E: %d, D: %d\n", errno, d); - d = opendir("/i/dont/exist"); - printf("--E: %d, D: %d\n", errno, d); - d = opendir("/test/freeforall.ext"); - printf("--E: %d, D: %d\n", errno, d); - while ((e = readdir(d))) puts(e->d_name); - printf("--E: %d\n", errno); - - return 0; - } - ''' - expected = ''' - --E: 0 - . - .. - hello - goodbye - freeforall.ext - restricted.ext - --E: 0 - **** - . - .. - .. - **** - --E: 0 - --E: 13, D: 0 - --E: 2, D: 0 - --E: 20, D: 0 - --E: 9 - ''' - self.do_run(src, re.sub('(^|\n)\s+', '\\1', expected), post_build=add_pre_run) + def test_readdir(self): + src = open(path_from_root('tests', 'dirent', 'test_readdir.c'), 'r').read() + self.do_run(src, 'success', force_c=True) def test_stat(self): add_pre_run = ''' @@ -6906,20 +6955,9 @@ def process(filename): self.do_run(src, expected, post_build=add_pre_run, extra_emscripten_args=['-H', 'libc/fcntl.h']) def test_fcntl_open(self): - add_pre_run = ''' -def process(filename): - src = open(filename, 'r').read().replace( - '// {{PRE_RUN_ADDITIONS}}', - \'\'\' - FS.createDataFile('/', 'test-file', 'abcdef', true, true); - FS.createFolder('/', 'test-folder', true, true); - \'\'\' - ) - open(filename, 'w').write(src) -''' src = open(path_from_root('tests', 'fcntl-open', 'src.c'), 'r').read() expected = open(path_from_root('tests', 'fcntl-open', 'output.txt'), 'r').read() - self.do_run(src, expected, post_build=add_pre_run, extra_emscripten_args=['-H', 'libc/fcntl.h']) + self.do_run(src, expected, force_c=True, extra_emscripten_args=['-H', 'libc/fcntl.h']) def test_fcntl_misc(self): add_pre_run = ''' @@ -7087,49 +7125,8 @@ def process(filename): self.do_run(src, re.sub('(^|\n)\s+', '\\1', expected)) def test_utime(self): - add_pre_run_and_checks = ''' -def process(filename): - src = open(filename, 'r').read().replace( - '// {{PRE_RUN_ADDITIONS}}', - \'\'\' - var TEST_F1 = FS.createFolder('/', 'writeable', true, true); - var TEST_F2 = FS.createFolder('/', 'unwriteable', true, false); - \'\'\' - ).replace( - '// {{POST_RUN_ADDITIONS}}', - \'\'\' - Module.print('first changed: ' + (TEST_F1.timestamp == 1200000000000)); - Module.print('second changed: ' + (TEST_F2.timestamp == 1200000000000)); - \'\'\' - ) - open(filename, 'w').write(src) -''' - src = r''' - #include <stdio.h> - #include <errno.h> - #include <utime.h> - - int main() { - struct utimbuf t = {1000000000, 1200000000}; - char* writeable = "/writeable"; - char* unwriteable = "/unwriteable"; - - utime(writeable, &t); - printf("writeable errno: %d\n", errno); - - utime(unwriteable, &t); - printf("unwriteable errno: %d\n", errno); - - return 0; - } - ''' - expected = ''' - writeable errno: 0 - unwriteable errno: 1 - first changed: true - second changed: false - ''' - self.do_run(src, re.sub('(^|\n)\s+', '\\1', expected), post_build=add_pre_run_and_checks) + src = open(path_from_root('tests', 'utime', 'test_utime.c'), 'r').read() + self.do_run(src, 'success', force_c=True) def test_utf(self): self.banned_js_engines = [SPIDERMONKEY_ENGINE] # only node handles utf well @@ -7221,38 +7218,6 @@ def process(filename): Settings.LINKABLE = linkable # regression check for issue #273 self.do_run(src, "1 2 3") - def test_readdir(self): - add_pre_run = ''' -def process(filename): - src = open(filename, 'r').read().replace( - '// {{PRE_RUN_ADDITIONS}}', - "FS.createFolder('', 'test', true, true);\\nFS.createLazyFile( 'test', 'some_file', 'http://localhost/some_file', true, false);\\nFS.createFolder('test', 'some_directory', true, true);" - ) - open(filename, 'w').write(src) -''' - - src = ''' - #include <dirent.h> - #include <stdio.h> - - int main() - { - DIR * dir; - dirent * entity; - - dir = opendir( "test" ); - - while( ( entity = readdir( dir ) ) ) - { - printf( "%s is a %s\\n", entity->d_name, entity->d_type & DT_DIR ? "directory" : "file" ); - } - - return 0; - } - - ''' - self.do_run(src, ". is a directory\n.. is a directory\nsome_file is a file\nsome_directory is a directory", post_build=add_pre_run) - def test_fs_base(self): Settings.INCLUDE_FULL_LIBRARY = 1 try: @@ -7309,18 +7274,8 @@ def process(filename): self.do_run(src, expected, extra_emscripten_args=['-H', 'libc/unistd.h']) def test_unistd_ttyname(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', 'ttyname.js'), 'r').read() - ) - open(filename, 'w').write(src) -''' src = open(path_from_root('tests', 'unistd', 'ttyname.c'), 'r').read() - expected = open(path_from_root('tests', 'unistd', 'ttyname.out'), 'r').read() - self.do_run(src, expected, post_build=add_pre_run) + self.do_run(src, 'success', force_c=True) def test_unistd_dup(self): src = open(path_from_root('tests', 'unistd', 'dup.c'), 'r').read() @@ -7352,18 +7307,8 @@ def process(filename): self.do_run(src, expected) def test_unistd_isatty(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', 'isatty.js'), 'r').read() - ) - open(filename, 'w').write(src) -''' src = open(path_from_root('tests', 'unistd', 'isatty.c'), 'r').read() - expected = open(path_from_root('tests', 'unistd', 'isatty.out'), 'r').read() - self.do_run(src, expected, post_build=add_pre_run) + self.do_run(src, 'success', force_c=True) def test_unistd_sysconf(self): src = open(path_from_root('tests', 'unistd', 'sysconf.c'), 'r').read() @@ -10020,97 +9965,97 @@ finalizing 3 (global == 0) ''') # Generate tests for everything - def make_run(fullname, name=-1, compiler=-1, embetter=0, quantum_size=0, typed_arrays=0, emcc_args=None, env='{}'): - exec(''' -class %s(T): - run_name = '%s' - env = %s + def make_run(fullname, name=-1, compiler=-1, embetter=0, quantum_size=0, + typed_arrays=0, emcc_args=None, env=None): - def tearDown(self): - super(%s, self).tearDown() + if env is None: env = {} - for k, v in self.env.iteritems(): - del os.environ[k] + TT = type(fullname, (T,), dict(run_name = fullname, env = env)) + + def tearDown(self): + super(TT, self).tearDown() + + for k, v in self.env.iteritems(): + del os.environ[k] + + TT.tearDown = tearDown + + def setUp(self): + super(TT, self).setUp() + for k, v in self.env.iteritems(): + assert k not in os.environ, k + ' should not be in environment' + os.environ[k] = v + + global checked_sanity + if not checked_sanity: + print '(checking sanity from test runner)' # do this after we set env stuff + check_sanity(force=True) + checked_sanity = True + + Building.COMPILER_TEST_OPTS = ['-g'] + os.chdir(self.get_dir()) # Ensure the directory exists and go there + Building.COMPILER = compiler + + self.emcc_args = None if emcc_args is None else emcc_args[:] + if self.emcc_args is not None: + Settings.load(self.emcc_args) + Building.LLVM_OPTS = 0 + if '-O2' in self.emcc_args: + Building.COMPILER_TEST_OPTS = [] # remove -g in -O2 tests, for more coverage + #Building.COMPILER_TEST_OPTS += self.emcc_args + for arg in self.emcc_args: + if arg.startswith('-O'): + Building.COMPILER_TEST_OPTS.append(arg) # so bitcode is optimized too, this is for cpp to ll + else: + try: + key, value = arg.split('=') + Settings[key] = value # forward -s K=V + except: + pass + return + + # TODO: Move much of these to a init() function in shared.py, and reuse that + Settings.USE_TYPED_ARRAYS = typed_arrays + Settings.INVOKE_RUN = 1 + Settings.RELOOP = 0 # we only do them in the "o2" pass + Settings.MICRO_OPTS = embetter + Settings.QUANTUM_SIZE = quantum_size + Settings.ASSERTIONS = 1-embetter + Settings.SAFE_HEAP = 1-embetter + Settings.CHECK_OVERFLOWS = 1-embetter + Settings.CORRECT_OVERFLOWS = 1-embetter + Settings.CORRECT_SIGNS = 0 + Settings.CORRECT_ROUNDINGS = 0 + Settings.CORRECT_OVERFLOWS_LINES = CORRECT_SIGNS_LINES = CORRECT_ROUNDINGS_LINES = SAFE_HEAP_LINES = [] + Settings.CHECK_SIGNS = 0 #1-embetter + Settings.RUNTIME_TYPE_INFO = 0 + Settings.DISABLE_EXCEPTION_CATCHING = 0 + Settings.INCLUDE_FULL_LIBRARY = 0 + Settings.BUILD_AS_SHARED_LIB = 0 + Settings.RUNTIME_LINKED_LIBS = [] + Settings.EMULATE_UNALIGNED_ACCESSES = int(Settings.USE_TYPED_ARRAYS == 2 and Building.LLVM_OPTS == 2) + Settings.DOUBLE_MODE = 1 if Settings.USE_TYPED_ARRAYS and Building.LLVM_OPTS == 0 else 0 + Settings.PRECISE_I64_MATH = 0 + Settings.NAMED_GLOBALS = 0 if not embetter else 1 + + TT.setUp = setUp - def setUp(self): - super(%s, self).setUp() - - for k, v in self.env.iteritems(): - assert k not in os.environ, k + ' should not be in environment' - os.environ[k] = v - - global checked_sanity - if not checked_sanity: - print '(checking sanity from test runner)' # do this after we set env stuff - check_sanity(force=True) - checked_sanity = True - - Building.COMPILER_TEST_OPTS = ['-g'] - os.chdir(self.get_dir()) # Ensure the directory exists and go there - Building.COMPILER = %r - - self.emcc_args = %s - if self.emcc_args is not None: - Settings.load(self.emcc_args) - Building.LLVM_OPTS = 0 - if '-O2' in self.emcc_args: - Building.COMPILER_TEST_OPTS = [] # remove -g in -O2 tests, for more coverage - #Building.COMPILER_TEST_OPTS += self.emcc_args - for arg in self.emcc_args: - if arg.startswith('-O'): - Building.COMPILER_TEST_OPTS.append(arg) # so bitcode is optimized too, this is for cpp to ll - else: - try: - key, value = arg.split('=') - Settings[key] = value # forward -s K=V - except: - pass - return - - embetter = %d - quantum_size = %d - # TODO: Move much of these to a init() function in shared.py, and reuse that - Settings.USE_TYPED_ARRAYS = %d - Settings.INVOKE_RUN = 1 - Settings.RELOOP = 0 # we only do them in the "o2" pass - Settings.MICRO_OPTS = embetter - Settings.QUANTUM_SIZE = quantum_size - Settings.ASSERTIONS = 1-embetter - Settings.SAFE_HEAP = 1-embetter - Settings.CHECK_OVERFLOWS = 1-embetter - Settings.CORRECT_OVERFLOWS = 1-embetter - Settings.CORRECT_SIGNS = 0 - Settings.CORRECT_ROUNDINGS = 0 - Settings.CORRECT_OVERFLOWS_LINES = CORRECT_SIGNS_LINES = CORRECT_ROUNDINGS_LINES = SAFE_HEAP_LINES = [] - Settings.CHECK_SIGNS = 0 #1-embetter - Settings.RUNTIME_TYPE_INFO = 0 - Settings.DISABLE_EXCEPTION_CATCHING = 0 - Settings.INCLUDE_FULL_LIBRARY = 0 - Settings.BUILD_AS_SHARED_LIB = 0 - Settings.RUNTIME_LINKED_LIBS = [] - Settings.EMULATE_UNALIGNED_ACCESSES = int(Settings.USE_TYPED_ARRAYS == 2 and Building.LLVM_OPTS == 2) - Settings.DOUBLE_MODE = 1 if Settings.USE_TYPED_ARRAYS and Building.LLVM_OPTS == 0 else 0 - Settings.PRECISE_I64_MATH = 0 - Settings.NAMED_GLOBALS = 0 if not embetter else 1 - -TT = %s -''' % (fullname, fullname, env, fullname, fullname, compiler, str(emcc_args), embetter, quantum_size, typed_arrays, fullname)) return TT # Make one run with the defaults - exec('default = make_run("default", compiler=CLANG, emcc_args=[])') + default = make_run("default", compiler=CLANG, emcc_args=[]) # Make one run with -O1, with safe heap - exec('o1 = make_run("o1", compiler=CLANG, emcc_args=["-O1", "-s", "ASM_JS=0", "-s", "SAFE_HEAP=1"])') + o1 = make_run("o1", compiler=CLANG, emcc_args=["-O1", "-s", "ASM_JS=0", "-s", "SAFE_HEAP=1"]) # Make one run with -O2, but without closure (we enable closure in specific tests, otherwise on everything it is too slow) - exec('o2 = make_run("o2", compiler=CLANG, emcc_args=["-O2", "-s", "ASM_JS=0", "-s", "JS_CHUNK_SIZE=1024"])') + o2 = make_run("o2", compiler=CLANG, emcc_args=["-O2", "-s", "ASM_JS=0", "-s", "JS_CHUNK_SIZE=1024"]) # asm.js - exec('asm1 = make_run("asm1", compiler=CLANG, emcc_args=["-O1", "-s", "CHECK_HEAP_ALIGN=1"])') - exec('asm2 = make_run("asm2", compiler=CLANG, emcc_args=["-O2"])') - exec('asm2g = make_run("asm2g", compiler=CLANG, emcc_args=["-O2", "-g", "-s", "ASSERTIONS=1", "--memory-init-file", "1"])') - exec('''asm2x86 = make_run("asm2x86", compiler=CLANG, emcc_args=["-O2", "-g", "-s", "CHECK_HEAP_ALIGN=1"], env='{"EMCC_LLVM_TARGET": "i386-pc-linux-gnu"}')''') + asm1 = make_run("asm1", compiler=CLANG, emcc_args=["-O1", "-s", "CHECK_HEAP_ALIGN=1"]) + asm2 = make_run("asm2", compiler=CLANG, emcc_args=["-O2"]) + asm2g = make_run("asm2g", compiler=CLANG, emcc_args=["-O2", "-g", "-s", "ASSERTIONS=1", "--memory-init-file", "1"]) + asm2x86 = make_run("asm2x86", compiler=CLANG, emcc_args=["-O2", "-g", "-s", "CHECK_HEAP_ALIGN=1"], env={"EMCC_LLVM_TARGET": "i386-pc-linux-gnu"}) # Make custom runs with various options for compiler, quantum, embetter, typed_arrays in [ @@ -10120,7 +10065,7 @@ TT = %s fullname = 's_0_%d%s%s' % ( embetter, '' if quantum == 4 else '_q' + str(quantum), '' if typed_arrays in [0, 1] else '_t' + str(typed_arrays) ) - exec('%s = make_run(fullname, %r,%r,%d,%d,%d)' % (fullname, fullname, compiler, embetter, quantum, typed_arrays)) + locals()[fullname] = make_run(fullname, fullname, compiler, embetter, quantum, typed_arrays) del T # T is just a shape for the specific subclasses, we don't test it itself @@ -13183,8 +13128,11 @@ Press any key to continue.''' self.btest('sdl_maprgba.c', reference='sdl_maprgba.png', reference_slack=3) def test_sdl_rotozoom(self): - shutil.copyfile(path_from_root('tests', 'screenshot.png'), os.path.join(self.get_dir(), 'example.png')) - self.btest('sdl_rotozoom.c', reference='sdl_rotozoom.png', args=['--preload-file', 'example.png']) + shutil.copyfile(path_from_root('tests', 'screenshot.png'), os.path.join(self.get_dir(), 'screenshot.png')) + self.btest('sdl_rotozoom.c', reference='sdl_rotozoom.png', args=['--preload-file', 'screenshot.png'], reference_slack=3) + + def test_sdl_gfx_primitives(self): + self.btest('sdl_gfx_primitives.c', reference='sdl_gfx_primitives.png', reference_slack=1) def test_sdl_canvas_palette_2(self): open(os.path.join(self.get_dir(), 'pre.js'), 'w').write(''' diff --git a/tests/sdl_gfx_primitives.c b/tests/sdl_gfx_primitives.c new file mode 100644 index 00000000..db0c6181 --- /dev/null +++ b/tests/sdl_gfx_primitives.c @@ -0,0 +1,46 @@ +#include "SDL/SDL.h" +#include "SDL/SDL_gfxPrimitives.h" + +#ifdef EMSCRIPTEN +#include "emscripten.h" +#endif + +int main(int argc, char **argv) { + SDL_Init(SDL_INIT_VIDEO); + + const int width = 400; + const int height = 400; + SDL_Surface *screen = SDL_SetVideoMode(width, height, 32, SDL_SWSURFACE); + boxColor(screen, 0, 0, width, height, 0xff); + + boxColor(screen, 0, 0, 98, 98, 0xff0000ff); + boxRGBA(screen, 100, 0, 198, 98, 0, 0, 0xff, 0xff); + // check that the x2 > x1 case is handled correctly + boxColor(screen, 298, 98, 200, 0, 0x00ff00ff); + boxColor(screen, 398, 98, 300, 0, 0xff0000ff); + + rectangleColor(screen, 0, 100, 98, 198, 0x000ffff); + rectangleRGBA(screen, 100, 100, 198, 198, 0xff, 0, 0, 0xff); + + ellipseColor(screen, 300, 150, 99, 49, 0x00ff00ff); + filledEllipseColor(screen, 100, 250, 99, 49, 0x00ff00ff); + filledEllipseRGBA(screen, 250, 300, 49, 99, 0, 0, 0xff, 0xff); + + lineColor(screen, 300, 200, 400, 300, 0x00ff00ff); + lineRGBA(screen, 300, 300, 400, 400, 0, 0xff, 0, 0xff); + + SDL_UpdateRect(screen, 0, 0, 0, 0); + +#ifndef EMSCRIPTEN + SDL_Event evt; + SDL_SaveBMP(screen, "native_output.bmp"); + while (1) { + if (SDL_PollEvent(&evt) != 0 && evt.type == SDL_QUIT) break; + SDL_Delay(33); + } +#endif + + SDL_Quit(); + + return 1; +} diff --git a/tests/sdl_gfx_primitives.png b/tests/sdl_gfx_primitives.png Binary files differnew file mode 100644 index 00000000..525b4f8f --- /dev/null +++ b/tests/sdl_gfx_primitives.png diff --git a/tests/sdl_rotozoom.c b/tests/sdl_rotozoom.c index b3970f6c..e81258d9 100644 --- a/tests/sdl_rotozoom.c +++ b/tests/sdl_rotozoom.c @@ -6,15 +6,18 @@ #include "emscripten.h" #endif +const int numSprites = 8; SDL_Surface *screen; -SDL_Surface *sprite[6]; +SDL_Surface *sprite[numSprites]; void mainloop() { int i; + int row = 0; SDL_Rect rect = { 0, 0, 100, 100 }; - for (i = 0; i < 6; i++) { + for (i = 0; i < numSprites; i++) { rect.x = i & 1 ? 200 : 0; - rect.y = i & 2 ? 200 : 0; + rect.y = row * 200; + row += i & 1; SDL_BlitSurface(sprite[i], 0, screen, &rect); SDL_UpdateRect(screen, 0, 0, 0, 0); } @@ -23,20 +26,27 @@ void mainloop() { int main(int argc, char **argv) { SDL_Init(SDL_INIT_VIDEO); - screen = SDL_SetVideoMode(400, 400, 32, SDL_SWSURFACE); + const int width = 400; + const int height = 200 * (numSprites + 1) / 2; + screen = SDL_SetVideoMode(width, height, 32, SDL_SWSURFACE); + SDL_Rect rect = { 0, 0, width, height }; + SDL_FillRect(screen, &rect, SDL_MapRGBA(screen->format, 0, 0, 0, 0xff)); - sprite[0] = IMG_Load("example.png"); + sprite[0] = IMG_Load("screenshot.png"); sprite[1] = SDL_CreateRGBSurface(SDL_SWSURFACE, 100, 100, 32, 0xFF000000, 0xFF0000, 0xFF00, 0xFF); SDL_FillRect(sprite[1], 0, 0xA0A0A0A0); sprite[2] = zoomSurface(sprite[0], 0.5, 0.5, SMOOTHING_ON); sprite[3] = zoomSurface(sprite[1], 0.5, 0.5, SMOOTHING_ON); sprite[4] = rotozoomSurface(sprite[0], -20, 0.3, SMOOTHING_ON); sprite[5] = rotozoomSurface(sprite[1], 45, 0.5, SMOOTHING_ON); + sprite[6] = zoomSurface(sprite[0], -0.5, 0.5, SMOOTHING_ON); + sprite[7] = zoomSurface(sprite[0], -0.5, -0.5, SMOOTHING_ON); mainloop(); #ifndef EMSCRIPTEN SDL_Event evt; + SDL_SaveBMP(screen, "native_output.bmp"); while (1) { if (SDL_PollEvent(&evt) != 0 && evt.type == SDL_QUIT) break; //mainloop(); diff --git a/tests/sdl_rotozoom.png b/tests/sdl_rotozoom.png Binary files differindex 3ca8da70..5c5dec2f 100644 --- a/tests/sdl_rotozoom.png +++ b/tests/sdl_rotozoom.png diff --git a/tests/unistd/isatty.c b/tests/unistd/isatty.c index cc1ff641..191036e9 100644 --- a/tests/unistd/isatty.c +++ b/tests/unistd/isatty.c @@ -1,28 +1,28 @@ -#include <stdio.h> +#include <assert.h> #include <errno.h> -#include <unistd.h> #include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> int main() { - printf("read: %d\n", isatty(open("/read", O_RDONLY))); - printf("errno: %d\n", errno); - errno = 0; + int err; + + err = isatty(-1); + assert(!err); + assert(errno == EBADF); - printf("write: %d\n", isatty(open("/write", O_WRONLY))); - printf("errno: %d\n", errno); - errno = 0; + err = isatty(open("/dev/stdin", O_RDONLY)); + assert(err == 1); - printf("all: %d\n", isatty(open("/all", O_RDONLY))); - printf("errno: %d\n", errno); - errno = 0; + err = isatty(open("/dev/null", O_RDONLY)); + assert(!err); - printf("folder: %d\n", isatty(open("/folder", O_RDONLY))); - printf("errno: %d\n", errno); - errno = 0; + err = isatty(open("/dev", O_RDONLY)); + assert(!err); - printf("file: %d\n", isatty(open("/file", O_RDONLY))); - printf("errno: %d\n", errno); - errno = 0; + puts("success"); - return 0; -} + return EXIT_SUCCESS; +}
\ No newline at end of file diff --git a/tests/unistd/isatty.js b/tests/unistd/isatty.js deleted file mode 100644 index d88bd2be..00000000 --- a/tests/unistd/isatty.js +++ /dev/null @@ -1,5 +0,0 @@ -FS.createDevice('/', 'read', function() {}, null); -FS.createDevice('/', 'write', null, function() {}); -FS.createDevice('/', 'all', function() {}, function() {}); -FS.createFolder('/', 'folder', true, true); -FS.createDataFile('/', 'file', 'test', true, true); diff --git a/tests/unistd/isatty.out b/tests/unistd/isatty.out deleted file mode 100644 index 6823c1f0..00000000 --- a/tests/unistd/isatty.out +++ /dev/null @@ -1,10 +0,0 @@ -read: 0 -errno: 25 -write: 0 -errno: 25 -all: 0 -errno: 25 -folder: 0 -errno: 25 -file: 0 -errno: 25 diff --git a/tests/unistd/ttyname.c b/tests/unistd/ttyname.c index 7080be5d..b7cc27bd 100644 --- a/tests/unistd/ttyname.c +++ b/tests/unistd/ttyname.c @@ -1,51 +1,43 @@ -#include <stdio.h> +#include <assert.h> #include <errno.h> -#include <unistd.h> #include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> int main() { + int err; + int stdin, null, dev; char buffer[256]; - int d = open("/device", O_RDWR); - int f = open("/", O_RDONLY); char* result; + stdin = open("/dev/stdin", O_RDONLY); + null = open("/dev/null", O_RDONLY); + dev = open("/dev", O_RDONLY); + result = ctermid(buffer); - if (result) { - printf("ctermid: %s\n", result); - } else { - printf("ctermid errno: %d\n", errno); - errno = 0; - } - - if (ttyname_r(d, buffer, 256) == 0) { - printf("ttyname_r(d, ..., 256): %s\n", buffer); - } else { - printf("ttyname_r(d, ..., 256) errno: %d\n", errno); - errno = 0; - } - - if (ttyname_r(d, buffer, 2) == 0) { - printf("ttyname_r(d, ..., 2): %s\n", buffer); - } else { - printf("ttyname_r(d, ..., 2) errno: %d\n", errno); - errno = 0; - } - - result = ttyname(d); - if (result) { - printf("ttyname(d): %s\n", result); - } else { - printf("ttyname(d) errno: %d\n", errno); - errno = 0; - } - - result = ttyname(f); - if (result) { - printf("ttyname(f): %s\n", result); - } else { - printf("ttyname(f) errno: %d\n", errno); - errno = 0; - } - - return 0; + assert(!strcmp(result, "/dev/tty")); + + // strstr instead of strcmp as native code may + // be using a virtual console (e.g. /dev/tty02) + err = ttyname_r(stdin, buffer, 256); + assert(!err); + assert(strstr(buffer, "/dev/tty")); + + err = ttyname_r(stdin, buffer, 2); + assert(err == ERANGE); + + result = ttyname(stdin); + assert(strstr(result, "/dev/tty")); + + result = ttyname(null); + assert(!result); + + result = ttyname(dev); + assert(!result); + + puts("success"); + + return EXIT_SUCCESS; } diff --git a/tests/unistd/ttyname.js b/tests/unistd/ttyname.js deleted file mode 100644 index a21f417f..00000000 --- a/tests/unistd/ttyname.js +++ /dev/null @@ -1 +0,0 @@ -FS.createDevice('/', 'device', function() {}, function() {}); diff --git a/tests/unistd/ttyname.out b/tests/unistd/ttyname.out deleted file mode 100644 index b47cbf75..00000000 --- a/tests/unistd/ttyname.out +++ /dev/null @@ -1,5 +0,0 @@ -ctermid: /dev/tty -ttyname_r(d, ..., 256): /device -ttyname_r(d, ..., 2) errno: 34 -ttyname(d): /device -ttyname(f) errno: 25 diff --git a/tests/utime/test_utime.c b/tests/utime/test_utime.c new file mode 100644 index 00000000..1793f4a5 --- /dev/null +++ b/tests/utime/test_utime.c @@ -0,0 +1,53 @@ +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <utime.h> +#include <sys/stat.h> + +void setup() { + mkdir("writeable", 0777); + mkdir("unwriteable", 0111); +} + +void cleanup() { + rmdir("writeable"); + rmdir("unwriteable"); +} + +void test() { + struct stat s; + // currently, the most recent timestamp is shared for atime, + // ctime and mtime. using unique values for each in the test + // will fail + struct utimbuf t = {1000000000, 1000000000}; + + utime("writeable", &t); + assert(!errno); + memset(&s, 0, sizeof s); + stat("writeable", &s); + assert(s.st_atime == t.actime); + assert(s.st_mtime == t.modtime); + + // write permissions aren't checked when setting node + // attributes unless the user uid isn't the owner (so + // therefor, this should work fine) + utime("unwriteable", &t); + assert(!errno); + memset(&s, 0, sizeof s); + stat("unwriteable", &s); + assert(s.st_atime == t.actime); + assert(s.st_mtime == t.modtime); + + puts("success"); +} + +int main() { + atexit(cleanup); + signal(SIGABRT, cleanup); + setup(); + test(); + return EXIT_SUCCESS; +}
\ No newline at end of file diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 06d82752..71a59921 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -136,6 +136,9 @@ var CONTROL_FLOW = set('do', 'while', 'for', 'if', 'switch'); var NAME_OR_NUM = set('name', 'num'); var ASSOCIATIVE_BINARIES = set('+', '*', '|', '&', '^'); +var BREAK_CAPTURERS = set('do', 'while', 'for', 'switch'); +var CONTINUE_CAPTURERS = LOOP; + var NULL_NODE = ['name', 'null']; var UNDEFINED_NODE = ['unary-prefix', 'void', ['num', 0]]; var TRUE_NODE = ['unary-prefix', '!', ['num', 0]]; @@ -559,7 +562,8 @@ function simplifyExpressionsPre(ast) { } else if (type === 'binary' && node[1] === '^') { // LLVM represents bitwise not as xor with -1. Translate it back to an actual bitwise not. if (node[3][0] === 'unary-prefix' && node[3][1] === '-' && node[3][2][0] === 'num' && - node[3][2][1] === 1) { + node[3][2][1] === 1 && + !(node[2][0] == 'unary-prefix' && node[2][1] == '~')) { // avoid creating ~~~ which is confusing for asm given the role of ~~ return ['unary-prefix', '~', node[2]]; } } else if (type === 'binary' && node[1] === '>>' && node[3][0] === 'num' && @@ -633,7 +637,8 @@ function simplifyExpressionsPre(ast) { if (node[0] === 'seq' && node[1][0] === 'assign' && node[1][2][0] === 'sub' && node[1][2][1][0] === 'name' && (node[1][2][1][1] === 'HEAP32' || node[1][2][1][1] === 'HEAPF32') && node[1][2][2][0] === 'binary' && node[1][2][2][2][0] === 'name' && node[1][2][2][2][1] === 'tempDoublePtr' && - node[1][3][0] === 'sub' && node[1][3][1][0] === 'name' && (node[1][3][1][1] === 'HEAP32' || node[1][3][1][1] === 'HEAPF32')) { + node[1][3][0] === 'sub' && node[1][3][1][0] === 'name' && (node[1][3][1][1] === 'HEAP32' || node[1][3][1][1] === 'HEAPF32') && + node[2][0] !== 'seq') { // avoid (x, y, z) which can be used for tempDoublePtr on doubles for alignment fixes if (node[1][2][1][1] === 'HEAP32') { node[1][3][1][1] = 'HEAPF32'; return ['unary-prefix', '+', node[1][3]]; @@ -1531,10 +1536,15 @@ function unVarify(vars, ret) { // transform var x=1, y=2 etc. into (x=1, y=2), i var ASM_INT = 0; var ASM_DOUBLE = 1; -function detectAsmCoercion(node) { +function detectAsmCoercion(node, asmInfo) { // for params, +x vs x|0, for vars, 0.0 vs 0 if (node[0] === 'num' && node[1].toString().indexOf('.') >= 0) return ASM_DOUBLE; - return node[0] === 'unary-prefix' ? ASM_DOUBLE : ASM_INT; + if (node[0] === 'unary-prefix') return ASM_DOUBLE; + if (asmInfo && node[0] == 'name') { + if (node[1] in asmInfo.vars) return asmInfo.vars[node[1]]; + if (node[1] in asmInfo.params) return asmInfo.params[node[1]]; + } + return ASM_INT; } function makeAsmParamCoercion(param, type) { @@ -2974,17 +2984,31 @@ function outline(ast) { for (var i = 0; i < stack.length; i++) { asmData.stackPos[stack[i]] = i*8; } - + // Reserve an extra two spots: one for control flow var, the other for control flow data + asmData.stackSize = (stack.length + 2)*8; + asmData.controlStackPos = asmData.stackSize - 16; + asmData.controlDataStackPos = asmData.stackSize - 8; asmData.splitCounter = 0; } // Analyze uses - reads and writes - of variables in part of the AST of a function function analyzeCode(func, asmData, ast) { + var labels = {}; + var labelCounter = 1; // 0 means no label + + traverse(ast, function(node, type) { + if ((type == 'label' || type in LOOP_FLOW) && node[1] && !(node[1] in labels)) { + labels[node[1]] = labelCounter++; + } + }); + var writes = {}; var appearances = {}; var hasReturn = false, hasBreak = false, hasContinue = false; var breaks = {}; // set of labels we break or continue var continues = {}; // to. '0' is an unlabeled one + var breakCapturers = 0; + var continueCapturers = 0; traverse(ast, function(node, type) { if (type == 'assign' && node[2][0] == 'name') { @@ -3001,11 +3025,31 @@ function outline(ast) { } else if (type == 'return') { hasReturn = true; } else if (type == 'break') { - breaks[node[1] || 0] = 0; + var label = node[1] || 0; + if (!label && breakCapturers > 0) return; // no label, and captured + if (label && (label in labels)) return; // label, and defined in this code, so captured + breaks[label || 0] = 0; hasBreak = true; } else if (type == 'continue') { - continues[node[1] || 0] = 0; + var label = node[1] || 0; + if (!label && continueCapturers > 0) return; // no label, and captured + if (label && (label in labels)) return; // label, and defined in this code, so captured + continues[label || 0] = 0; hasContinue = true; + } else { + if (type in BREAK_CAPTURERS) { + breakCapturers++; + } + if (type in CONTINUE_CAPTURERS) { + continueCapturers++; + } + } + }, function(node, type) { + if (type in BREAK_CAPTURERS) { + breakCapturers--; + } + if (type in CONTINUE_CAPTURERS) { + continueCapturers--; } }); @@ -3015,9 +3059,26 @@ function outline(ast) { if (appearances[name] > 0) reads[name] = 0; } - return { writes: writes, reads: reads, hasReturn: hasReturn, breaks: breaks, continues: continues }; + return { writes: writes, reads: reads, hasReturn: hasReturn, breaks: breaks, continues: continues, labels: labels }; + } + + function makeAssign(dst, src) { + return ['assign', true, dst, src]; + } + function makeStackAccess(type, pos) { // TODO: float64, not 32 + return ['sub', ['name', type == ASM_INT ? 'HEAP32' : 'HEAPF32'], ['binary', '>>', ['binary', '+', ['name', 'sp'], ['num', pos]], ['num', '2']]]; + } + function makeIf(cond, then, else_) { + var ret = ['if', cond, ['block', then]]; + if (else_) ret.push(['block', else_]); + return ret; + } + function makeComparison(left, comp, right) { + return ['binary', comp, left, right]; } + var CONTROL_BREAK = 1, CONTROL_BREAK_LABEL = 2, CONTROL_CONTINUE = 3, CONTROL_CONTINUE_LABEL = 4, CONTROL_RETURN_VOID = 5, CONTROL_RETURN_INT = 6, CONTROL_RETURN_DOUBLE = 7; + var sizeToOutline = extraInfo.sizeToOutline; var level = 0; @@ -3025,7 +3086,7 @@ function outline(ast) { printErr(' do outline ' + [func[1], level, 'range:', start, end, 'of', stats.length]); var code = stats.slice(start, end+1); var newIdent = func[1] + '$' + (asmData.splitCounter++); - // add spills and reads before and after the call to the outlined code + // add spills and reads before and after the call to the outlined code, and in the outlined code itself var codeInfo = analyzeCode(func, asmData, code); var reps = []; for (var v in codeInfo.reads) { @@ -3039,21 +3100,109 @@ function outline(ast) { reps.push(['stat', ['assign', true, ['name', v], ['sub', ['name', getAsmType(asmData, v) == ASM_INT ? 'HEAP32' : 'HEAPF32'], ['binary', '>>', ['binary', '+', ['name', 'sp'], ['num', asmData.stackPos[v]]], ['num', '2']]]]]); code.push(['stat', ['assign', true, ['sub', ['name', getAsmType(asmData, v) == ASM_INT ? 'HEAP32' : 'HEAPF32'], ['binary', '>>', ['binary', '+', ['name', 'sp'], ['num', asmData.stackPos[v]]], ['num', '2']]], ['name', v]]]); } - stats.splice.apply(stats, [start, end-start+1].concat(reps)); // Generate new function if (codeInfo.hasReturn || codeInfo.hasBreak || codeInfo.hasContinue) { // we need to capture all control flow using a top-level labeled one-time loop in the outlined function code = [['label', 'OL', ['do', ['num', 0], ['block', code]]]]; + var breakCapturers = 0; + var continueCapturers = 0; + traverse(code, function(node, type) { + // replace all break/continue/returns with code to break out of the main one-time loop, and set the control data + if (type == 'return') { + var ret = ['break', 'OL']; + if (!node[1]) { + ret = ['seq', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos), ['num', CONTROL_RETURN_VOID]), ret]; + } else { + var type = detectAsmCoercion(node[1], asmData); + ret = ['seq', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos), ['num', type == ASM_INT ? CONTROL_RETURN_INT : CONTROL_RETURN_DOUBLE]), ret]; + ret = ['seq', makeAssign(makeStackAccess(type, asmData.controlDataStackPos), node[1]), ret]; + } + return ret; + } else if (type == 'break') { + var label = node[1] || 0; + if (label == 'OL') return; // this was just added before us, it is new replacement code + if (!label && breakCapturers > 0) return; // no label, and captured + if (label && (label in codeInfo.labels)) return; // label, and defined in this code, so captured + var ret = ['break', 'OL']; + ret = ['seq', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos), ['num', label ? CONTROL_BREAK_LABEL : CONTROL_BREAK]), ret]; + if (label) { + assert(label in codeInfo.labels, label + ' in ' + keys(codeInfo.labels)); + ret = ['seq', makeAssign(makeStackAccess(ASM_INT, asmData.controlDataStackPos), ['num', codeInfo.labels[label]]), ret]; + } + return ret; + } else if (type == 'continue') { + var label = node[1] || 0; + if (!label && continueCapturers > 0) return; // no label, and captured + if (label && (label in codeInfo.labels)) return; // label, and defined in this code, so captured + var ret = ['break', 'OL']; + ret = ['seq', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos), ['num', label ? CONTROL_CONTINUE_LABEL : CONTROL_CONTINUE]), ret]; + if (label) { + ret = ['seq', makeAssign(makeStackAccess(ASM_INT, asmData.controlDataStackPos), ['num', codeInfo.labels[label]]), ret]; + } + return ret; + } else { + if (type in BREAK_CAPTURERS) { + breakCapturers++; + } + if (type in CONTINUE_CAPTURERS) { + continueCapturers++; + } + } + }, function(node, type) { + if (type in BREAK_CAPTURERS) { + breakCapturers--; + } + if (type in CONTINUE_CAPTURERS) { + continueCapturers--; + } + }); + // read the control data at the callsite to the outlined function + if (codeInfo.hasReturn) { + reps.push(makeIf( + makeComparison(makeStackAccess(ASM_INT, asmData.controlStackPos), '==', ['num', CONTROL_RETURN_VOID]), + [['stat', ['return']]] + )); + reps.push(makeIf( + makeComparison(makeStackAccess(ASM_INT, asmData.controlStackPos), '==', ['num', CONTROL_RETURN_INT]), + [['stat', ['return', makeStackAccess(ASM_INT, asmData.controlDataStackPos)]]] + )); + reps.push(makeIf( + makeComparison(makeStackAccess(ASM_INT, asmData.controlStackPos), '==', ['num', CONTROL_RETURN_DOUBLE]), + [['stat', ['return', makeStackAccess(ASM_DOUBLE, asmData.controlDataStackPos)]]] + )); + } + if (codeInfo.hasBreak) { + reps.push(makeIf( + makeComparison(makeStackAccess(ASM_INT, asmData.controlStackPos), '==', ['num', CONTROL_BREAK]), + ['stat', ['break']] + )); + reps.push(makeIf( + makeComparison(makeStackAccess(ASM_INT, asmData.controlStackPos), '==', ['num', CONTROL_BREAK_LABEL]), + ['stat', ['break', makeStackAccess(ASM_INT, asmData.controlDataStackPos)]] // XXX here and below, need a switch overall possible labels + )); + } + if (codeInfo.hasContinue) { + reps.push(makeIf( + makeComparison(makeStackAccess(ASM_INT, asmData.controlStackPos), '==', ['num', CONTROL_CONTINUE]), + ['stat', ['break']] + )); + reps.push(makeIf( + makeComparison(makeStackAccess(ASM_INT, asmData.controlStackPos), '==', ['num', CONTROL_CONTINUE_LABEL]), + ['stat', ['continue', makeStackAccess(ASM_INT, asmData.controlDataStackPos)]] // XXX + )); + } } var newFunc = ['defun', newIdent, ['sp'], code]; - var newAsmInfo = { params: { sp: ASM_INT }, vars: {} }; + var newAsmData = { params: { sp: ASM_INT }, vars: {} }; for (var v in codeInfo.reads) { - newAsmInfo.vars[v] = getAsmType(asmData, v); + newAsmData.vars[v] = getAsmType(asmData, v); } for (var v in codeInfo.writes) { - newAsmInfo.vars[v] = getAsmType(asmData, v); + newAsmData.vars[v] = getAsmType(asmData, v); } - denormalizeAsm(newFunc, newAsmInfo); + denormalizeAsm(newFunc, newAsmData); + // replace in stats + stats.splice.apply(stats, [start, end-start+1].concat(reps)); return [newFunc]; } @@ -3138,7 +3287,7 @@ function outline(ast) { if (newFuncs.length > 0) { // We have outlined. Add stack support: header in which we allocate enough stack space TODO - // If sp was not present before, add it and before each return, pop the stack TODO + // If sp was not present before, add it and before each return, pop the stack. also a final pop if not ending with a return TODO // (none of this should be done in inner functions, of course, just the original) // add new functions to the toplevel, or create a toplevel if there isn't one @@ -3175,12 +3324,13 @@ function fixDotZero(js) { }); } -function asmLoopOptimizer(ast) { +function asmLastOpts(ast) { traverseGeneratedFunctions(ast, function(fun) { - // This is at the end of the pipeline, we can assume all other optimizations are done, and we modify loops - // into shapes that might confuse other passes traverse(fun, function(node, type) { if (type === 'while' && node[1][0] === 'num' && node[1][1] === 1 && node[2][0] === 'block') { + // This is at the end of the pipeline, we can assume all other optimizations are done, and we modify loops + // into shapes that might confuse other passes + // while (1) { .. if (..) { break } } ==> do { .. } while(..) var stats = node[2][1]; var last = stats[stats.length-1]; @@ -3208,6 +3358,11 @@ function asmLoopOptimizer(ast) { node[1] = simplifyNotCompsDirect(['unary-prefix', '!', conditionToBreak]); return node; } + } else if (type == 'binary' && node[1] == '&' && node[3][0] == 'unary-prefix' && node[3][1] == '-' && node[3][2][0] == 'num' && node[3][2][1] == 1) { + // Change &-1 into |0, at this point the hint is no longer needed + node[1] = '|'; + node[3] = node[3][2]; + node[3][1] = 0; } }); }); @@ -3266,7 +3421,7 @@ arguments_.slice(1).forEach(function(arg) { passes[arg](ast); }); if (asm && last) { - asmLoopOptimizer(ast); // TODO: move out of last, to make last faster when done later (as in side modules) + asmLastOpts(ast); // TODO: move out of last, to make last faster when done later (as in side modules) prepDotZero(ast); } var js = astToSrc(ast, minifyWhitespace), old; diff --git a/tools/shared.py b/tools/shared.py index 46245fd1..0351a736 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -257,7 +257,7 @@ def check_clang_version(): def check_llvm_version(): try: - check_clang_version(); + check_clang_version() except Exception, e: logging.warning('Could not verify LLVM version: %s' % str(e)) @@ -1196,7 +1196,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e opts.append('-jump-threading') opts.append('-correlated-propagation') opts.append('-dse') - #addExtensionsToPM(EP_ScalarOptimizerLate, MPM); + #addExtensionsToPM(EP_ScalarOptimizerLate, MPM) opts.append('-adce') opts.append('-simplifycfg') @@ -1300,7 +1300,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e '-O' + str(opt_level), '--closure', '0'], raw) f = open(relooper, 'w') f.write("// Relooper, (C) 2012 Alon Zakai, MIT license, https://github.com/kripken/Relooper\n") - f.write("var Relooper = (function() {\n"); + f.write("var Relooper = (function() {\n") f.write(open(raw).read()) f.write('\n return Module.Relooper;\n') f.write('})();\n') diff --git a/tools/test-js-optimizer-asm-last-output.js b/tools/test-js-optimizer-asm-last-output.js index 0f95d544..f850b18f 100644 --- a/tools/test-js-optimizer-asm-last-output.js +++ b/tools/test-js-optimizer-asm-last-output.js @@ -30,6 +30,7 @@ function finall(x) { a = -999999984306749400.0; a = -999999984306749400.0; a = -0xde0b6b000000000; + f(g() | 0); return 12.0e10; } function looop() { diff --git a/tools/test-js-optimizer-asm-last.js b/tools/test-js-optimizer-asm-last.js index 05e1049e..1d39b1a6 100644 --- a/tools/test-js-optimizer-asm-last.js +++ b/tools/test-js-optimizer-asm-last.js @@ -30,6 +30,7 @@ function finall(x) { a = +-0xde0b6b000000000; a = -+0xde0b6b000000000; a = -0xde0b6b000000000; + f(g() & -1); return +12e10; } function looop() { diff --git a/tools/test-js-optimizer-asm-pre-output.js b/tools/test-js-optimizer-asm-pre-output.js index b7afab26..8a5803d6 100644 --- a/tools/test-js-optimizer-asm-pre-output.js +++ b/tools/test-js-optimizer-asm-pre-output.js @@ -66,6 +66,7 @@ function b($this, $__n) { HEAP32[$4] = ~HEAP32[$5]; HEAP32[$4] = ~HEAP32[$5]; HEAP32[$4] = ~HEAP32[$5]; + h(~~g ^ -1); return; } function rett() { @@ -156,6 +157,8 @@ function tempDoublePtr($45, $14, $28, $42) { unelim2 = 127 + $14 | 0; func(); HEAP32[4] = unelim2; + barrier(); + $f163 = (HEAP32[tempDoublePtr >> 2] = HEAP32[$f165 >> 2], HEAP32[tempDoublePtr + 4 >> 2] = HEAP32[$f165 + 4 >> 2], +HEAPF64[tempDoublePtr >> 3]); } function boxx($this, $aabb, $xf, $childIndex) { $this = $this | 0; diff --git a/tools/test-js-optimizer-asm-pre.js b/tools/test-js-optimizer-asm-pre.js index b762a60e..5c004342 100644 --- a/tools/test-js-optimizer-asm-pre.js +++ b/tools/test-js-optimizer-asm-pre.js @@ -70,6 +70,7 @@ function b($this, $__n) { HEAP32[$4] = HEAP32[$5]^-1; // Rewrite to ~ and eliminate the |0. HEAP32[$4] = ((HEAP32[$5]|0)^-1)|0; + h((~~g) ^ -1); // do NOT convert this, as it would lead to ~~~ which is confusing in asm, given the role of ~~ return; } function rett() { @@ -166,6 +167,8 @@ function tempDoublePtr($45, $14, $28, $42) { unelim2 = (HEAP32[tempDoublePtr >> 2] = 127 + $14, +HEAPF32[tempDoublePtr >> 2]); func(); HEAPF32[4] = unelim2; + barrier(); + $f163 = (HEAP32[tempDoublePtr >> 2] = HEAP32[$f165 >> 2], HEAP32[tempDoublePtr + 4 >> 2] = HEAP32[$f165 + 4 >> 2], +HEAPF64[tempDoublePtr >> 3]); } function boxx($this, $aabb, $xf, $childIndex) { $this = $this | 0; |