diff --git a/AUTHORS b/AUTHORS index 604bd6bdf5a8a..d8f9da02678e3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,4 +1,4 @@ -The following authors have all licensed their contributions to Emscripten +The following authors have all licensed their contributions to Emscripten under the licensing terms detailed in LICENSE. (Authors keep copyright of their contributions, of course; they just grant @@ -97,4 +97,6 @@ a license to everyone to use it as detailed in LICENSE.) * Charlie Birks <admin@daftgames.net> * Ranger Harke <ranger.harke@autodesk.com> (copyright owned by Autodesk, Inc.) * Tobias Vrinssen <tobias@vrinssen.de> +* Patrick R. Martin <patrick.martin.r@gmail.com> +* Richard Quirk <richard.quirk@gmail.com> diff --git a/emcc b/emcc index 9a687bd8cd36b..95d77f10eb682 100755 --- a/emcc +++ b/emcc @@ -238,6 +238,9 @@ Options that are modified or new in %s include: (see --llvm-opts), setting this has no effect. + Note that LLVM LTO is not perfectly stable yet, + and can can cause code to behave incorrectly. + --closure <on> 0: No closure compiler (default in -O2 and below) 1: Run closure compiler. This greatly reduces code size and may in some cases increase @@ -1279,6 +1282,10 @@ try: os.path.join('libc', 'gen', 'vwarnx.c'), os.path.join('libc', 'stdlib', 'strtod.c'), ] + musl_files = [ + ] + for directory, sources in musl_files: + libc_files += [os.path.join('libc', 'musl', 'src', directory, source) for source in sources] return build_libc('libc.bc', libc_files) def apply_libc(need): @@ -1316,6 +1323,32 @@ try: 'wctrans.c', 'wcwidth.c', ]], + ['locale', [ + 'iswalnum_l.c', + 'iswalpha_l.c', + 'iswblank_l.c', + 'iswcntrl_l.c', + 'iswctype_l.c', + 'iswdigit_l.c', + 'iswgraph_l.c', + 'iswlower_l.c', + 'iswprint_l.c', + 'iswpunct_l.c', + 'iswspace_l.c', + 'iswupper_l.c', + 'iswxdigit_l.c', + 'strfmon.c', + 'strxfrm.c', + 'towctrans_l.c', + 'towlower_l.c', + 'towupper_l.c', + 'wcscoll.c', + 'wcscoll_l.c', + 'wcsxfrm.c', + 'wcsxfrm_l.c', + 'wctrans_l.c', + 'wctype_l.c', + ]], ['multibyte', [ 'btowc.c', 'mblen.c', @@ -1333,6 +1366,14 @@ try: 'wctob.c', 'wctomb.c', ]], + ['stdio', [ + 'fwprintf.c', + 'swprintf.c', + 'vfwprintf.c', + 'vswprintf.c', + 'vwprintf.c', + 'wprintf.c', + ]], ['stdlib', [ 'ecvt.c', 'fcvt.c', @@ -1342,7 +1383,7 @@ try: 'wcpcpy.c', 'wcpncpy.c', 'wcscasecmp.c', - # 'wcscasecmp_l.c', # XXX: alltypes.h issue + 'wcscasecmp_l.c', 'wcscat.c', 'wcschr.c', 'wcscmp.c', @@ -1351,7 +1392,7 @@ try: 'wcsdup.c', 'wcslen.c', 'wcsncasecmp.c', - # 'wcsncasecmp_l.c', # XXX: alltypes.h issue + 'wcsncasecmp_l.c', 'wcsncat.c', 'wcsncmp.c', 'wcsncpy.c', diff --git a/emscripten.py b/emscripten.py index 4d744fdd64f46..19e2160d4420b 100755 --- a/emscripten.py +++ b/emscripten.py @@ -39,7 +39,7 @@ def scan(ll, settings): if len(blockaddrs) > 0: settings['NECESSARY_BLOCKADDRS'] = blockaddrs -NUM_CHUNKS_PER_CORE = 1.25 +NUM_CHUNKS_PER_CORE = 1.0 MIN_CHUNK_SIZE = 1024*1024 MAX_CHUNK_SIZE = float(os.environ.get('EMSCRIPT_MAX_CHUNK_SIZE') or 'inf') # configuring this is just for debugging purposes @@ -209,7 +209,7 @@ def save_settings(): if cores > 1: intended_num_chunks = int(round(cores * NUM_CHUNKS_PER_CORE)) chunk_size = max(MIN_CHUNK_SIZE, total_ll_size / intended_num_chunks) - chunk_size += 3*len(meta) + len(forwarded_data)/3 # keep ratio of lots of function code to meta (expensive to process, and done in each parallel task) and forwarded data (less expensive but potentially significant) + chunk_size += 3*len(meta) # keep ratio of lots of function code to meta (expensive to process, and done in each parallel task) chunk_size = min(MAX_CHUNK_SIZE, chunk_size) else: chunk_size = MAX_CHUNK_SIZE # if 1 core, just use the max chunk size diff --git a/src/intertyper.js b/src/intertyper.js index 0f189863d8e91..e43cc298138e1 100644 --- a/src/intertyper.js +++ b/src/intertyper.js @@ -167,9 +167,9 @@ function intertyper(lines, sidePass, baseLineNums) { dprint('framework', 'Big picture: Starting intertyper, main pass=' + mainPass); - var unparsedBundles = []; + var finalResults = []; - // Line splitter. We break off some bunches of lines into unparsedBundles, which are + // Line splitter. We break off some bunches of lines into unparsed bundles, which are // parsed in separate passes later. This helps to keep memory usage low - we can start // from raw lines and end up with final JS for each function individually that way, instead // of intertyping them all, then analyzing them all, etc. @@ -185,12 +185,12 @@ function intertyper(lines, sidePass, baseLineNums) { intertype: 'unparsedTypes', lines: [] }; - unparsedBundles.push(unparsedTypes); + finalResults.push(unparsedTypes); unparsedGlobals = { intertype: 'unparsedGlobals', lines: [] }; - unparsedBundles.push(unparsedGlobals); + finalResults.push(unparsedGlobals); } var baseLineNumPosition = 0; for (var i = 0; i < lines.length; i++) { @@ -258,7 +258,7 @@ function intertyper(lines, sidePass, baseLineNums) { var ident = toNiceIdent(func.ident); if (!(ident in DEAD_FUNCTIONS)) { - unparsedBundles.push({ + finalResults.push({ intertype: 'unparsedFunction', // We need this early, to know basic function info - ident, params, varargs ident: ident, @@ -370,8 +370,6 @@ function intertyper(lines, sidePass, baseLineNums) { throw 'Invalid token, cannot triage: ' + dump(item); } - var extraResults = []; - // Line parsers to intermediate form // globals: type or variable @@ -719,7 +717,7 @@ function intertyper(lines, sidePass, baseLineNums) { var result = makeCall.call(this, item, 'invoke'); if (DISABLE_EXCEPTION_CATCHING == 1) { result.item.intertype = 'call'; - extraResults.push({ + finalResults.push({ intertype: 'branch', label: result.item.toLabel, lineNum: (result.forward ? item.parentLineNum : item.lineNum) + 0.5 @@ -988,13 +986,13 @@ function intertyper(lines, sidePass, baseLineNums) { // Input - var ret = lineSplitter().map(tokenizer).map(triager).filter(function(result) { - if (!result) return false; - if (result.tokens) result.tokens = null; // We do not need tokens, past the intertyper. Clean them up as soon as possible here. - return true; + lineSplitter().forEach(function(line) { + var t = tokenizer(line); + var item = triager(t); + if (!item) return; + finalResults.push(item); + if (item.tokens) item.tokens = null; // We do not need tokens, past the intertyper. Clean them up as soon as possible here. }); - if (unparsedBundles.length > 0) ret = ret.concat(unparsedBundles); - if (extraResults.length > 0) ret = ret.concat(extraResults); - return ret; + return finalResults; } diff --git a/src/jsifier.js b/src/jsifier.js index a8c8d32d14cc1..96cb8d9a3d574 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -675,10 +675,10 @@ function JSify(data, functionsOnly, givenFunctions) { ret += indent + 'if (Date.now() - START_TIME >= ' + (EXECUTION_TIMEOUT*1000) + ') throw "Timed out!" + (new Error().stack);\n'; } - if (PRINT_SPLIT_FILE_MARKER && Debugging.on && Debugging.getAssociatedSourceFile(line.lineNum)) { + if (PRINT_SPLIT_FILE_MARKER && Debugging.on && Debugging.getAssociatedSourceFile(label.lines[label.lines.length-1].lineNum)) { // Overwrite the associated source file for every line. The last line should contain the source file associated to // the return value/address of outer most block (the marked function). - associatedSourceFile = Debugging.getAssociatedSourceFile(line.lineNum); + associatedSourceFile = Debugging.getAssociatedSourceFile(label.lines[label.lines.length-1].lineNum); } // for special labels we care about (for phi), mark that we visited them @@ -1114,7 +1114,8 @@ function JSify(data, functionsOnly, givenFunctions) { if (!useIfs) { ret += 'switch(' + signedIdent + ') {\n'; } - for (var targetLabel in targetLabels) { + // process target labels, sorting them so output is consistently ordered + keys(targetLabels).sort().forEach(function(targetLabel) { if (!first && useIfs) { ret += 'else '; } else { @@ -1142,7 +1143,7 @@ function JSify(data, functionsOnly, givenFunctions) { labelJS: phiSet }); } - } + }); var phiSet = item.defaultLabelJS = getPhiSetsForLabel(phiSets, item.defaultLabel); if (useIfs) { if (item.switchLabels.length > 0) ret += 'else {\n'; @@ -1237,8 +1238,8 @@ function JSify(data, functionsOnly, givenFunctions) { var param1 = finalizeLLVMParameter(item.params[0]); var param2 = finalizeLLVMParameter(item.params[1]); switch (item.op) { - case 'add': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, 'tempValue+' + param2, type, null, null, null, null, ',') + ',tempValue)'; - case 'sub': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, 'tempValue-' + param2, type, null, null, null, null, ',') + ',tempValue)'; + case 'add': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, asmCoercion('tempValue+' + param2, type), type, null, null, null, null, ',') + ',tempValue)'; + case 'sub': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, asmCoercion('tempValue-' + param2, type), type, null, null, null, null, ',') + ',tempValue)'; case 'or': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, 'tempValue|' + param2, type, null, null, null, null, ',') + ',tempValue)'; case 'and': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, 'tempValue&' + param2, type, null, null, null, null, ',') + ',tempValue)'; case 'xor': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, 'tempValue^' + param2, type, null, null, null, null, ',') + ',tempValue)'; diff --git a/src/library.js b/src/library.js index 5c2c858db05db..a01153738d128 100644 --- a/src/library.js +++ b/src/library.js @@ -1974,6 +1974,7 @@ LibraryManager.library = { var flagLeftAlign = false; var flagAlternative = false; var flagZeroPad = false; + var flagPadSign = false; flagsLoop: while (1) { switch (next) { case {{{ charCode('+') }}}: @@ -1992,6 +1993,9 @@ LibraryManager.library = { flagZeroPad = true; break; } + case {{{ charCode(' ') }}}: + flagPadSign = true; + break; default: break flagsLoop; } @@ -2158,14 +2162,20 @@ LibraryManager.library = { } // Add sign if needed - if (flagAlwaysSigned) { - if (currArg < 0) { - prefix = '-' + prefix; - } else { + if (currArg >= 0) { + if (flagAlwaysSigned) { prefix = '+' + prefix; + } else if (flagPadSign) { + prefix = ' ' + prefix; } } + // Move sign to prefix so we zero-pad after the sign + if (argText.charAt(0) == '-') { + prefix = '-' + prefix; + argText = argText.substr(1); + } + // Add padding. while (prefix.length + argText.length < width) { if (flagLeftAlign) { @@ -2248,8 +2258,12 @@ LibraryManager.library = { if (next == {{{ charCode('E') }}}) argText = argText.toUpperCase(); // Add sign. - if (flagAlwaysSigned && currArg >= 0) { - argText = '+' + argText; + if (currArg >= 0) { + if (flagAlwaysSigned) { + argText = '+' + argText; + } else if (flagPadSign) { + argText = ' ' + argText; + } } } @@ -2887,6 +2901,13 @@ LibraryManager.library = { asprintf: function(s, format, varargs) { return _sprintf(-s, format, varargs); }, + dprintf__deps: ['_formatString', 'write'], + dprintf: function(fd, format, varargs) { + var result = __formatString(format, varargs); + var stack = Runtime.stackSave(); + var ret = _write(fd, allocate(result, 'i8', ALLOC_STACK), result.length); + Runtime.stackRestore(stack); + }, #if TARGET_X86 // va_arg is just like our varargs @@ -2895,6 +2916,7 @@ LibraryManager.library = { vprintf: 'printf', vsprintf: 'sprintf', vasprintf: 'asprintf', + vdprintf: 'dprintf', vscanf: 'scanf', vfscanf: 'fscanf', vsscanf: 'sscanf', @@ -2922,6 +2944,10 @@ LibraryManager.library = { vasprintf: function(s, format, va_arg) { return _asprintf(s, format, {{{ makeGetValue('va_arg', 0, '*') }}}); }, + vdprintf__deps: ['dprintf'], + vdprintf: function (fd, format, va_arg) { + return _dprintf(fd, format, {{{ makeGetValue('va_arg', 0, '*') }}}); + }, vscanf__deps: ['scanf'], vscanf: function(format, va_arg) { return _scanf(format, {{{ makeGetValue('va_arg', 0, '*') }}}); @@ -3792,6 +3818,7 @@ LibraryManager.library = { }, // We always assume ASCII locale. strcoll: 'strcmp', + strcoll_l: 'strcmp', strcasecmp__asm: true, strcasecmp__sig: 'iii', @@ -4058,6 +4085,7 @@ LibraryManager.library = { } }, _toupper: 'toupper', + toupper_l: 'toupper', tolower__asm: true, tolower__sig: 'ii', @@ -4068,54 +4096,65 @@ LibraryManager.library = { return (chr - {{{ charCode('A') }}} + {{{ charCode('a') }}})|0; }, _tolower: 'tolower', + tolower_l: 'tolower', // The following functions are defined as macros in glibc. islower: function(chr) { return chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}; }, + islower_l: 'islower', isupper: function(chr) { return chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('Z') }}}; }, + isupper_l: 'isupper', isalpha: function(chr) { return (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}) || (chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('Z') }}}); }, + isalpha_l: 'isalpha', isdigit: function(chr) { return chr >= {{{ charCode('0') }}} && chr <= {{{ charCode('9') }}}; }, - isdigit_l: 'isdigit', // no locale support yet + isdigit_l: 'isdigit', isxdigit: function(chr) { return (chr >= {{{ charCode('0') }}} && chr <= {{{ charCode('9') }}}) || (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('f') }}}) || (chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('F') }}}); }, - isxdigit_l: 'isxdigit', // no locale support yet + isxdigit_l: 'isxdigit', isalnum: function(chr) { return (chr >= {{{ charCode('0') }}} && chr <= {{{ charCode('9') }}}) || (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}) || (chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('Z') }}}); }, + isalnum_l: 'isalnum', ispunct: function(chr) { return (chr >= {{{ charCode('!') }}} && chr <= {{{ charCode('/') }}}) || (chr >= {{{ charCode(':') }}} && chr <= {{{ charCode('@') }}}) || (chr >= {{{ charCode('[') }}} && chr <= {{{ charCode('`') }}}) || (chr >= {{{ charCode('{') }}} && chr <= {{{ charCode('~') }}}); }, + ispunct_l: 'ispunct', isspace: function(chr) { return (chr == 32) || (chr >= 9 && chr <= 13); }, + isspace_l: 'isspace', isblank: function(chr) { return chr == {{{ charCode(' ') }}} || chr == {{{ charCode('\t') }}}; }, + isblank_l: 'isblank', iscntrl: function(chr) { return (0 <= chr && chr <= 0x1F) || chr === 0x7F; }, + iscntrl_l: 'iscntrl', isprint: function(chr) { return 0x1F < chr && chr < 0x7F; }, + isprint_l: 'isprint', isgraph: function(chr) { return 0x20 < chr && chr < 0x7F; }, + isgraph_l: 'isgraph', // Lookup tables for glibc ctype implementation. __ctype_b_loc: function() { // http://refspecs.freestandards.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/baselib---ctype-b-loc.html @@ -6435,11 +6474,15 @@ LibraryManager.library = { // locale.h // ========================================================================== + newlocale__deps: ['malloc'], newlocale: function(mask, locale, base) { - return 0; + return _malloc({{{ QUANTUM_SIZE}}}); }, - freelocale: function(locale) {}, + freelocale__deps: ['free'], + freelocale: function(locale) { + _free(locale); + }, uselocale: function(locale) { return 0; @@ -8854,7 +8897,7 @@ function autoAddDeps(object, name) { } // Add aborting stubs for various libc stuff needed by libc++ -['pthread_cond_signal', 'pthread_equal', 'wcstol', 'wcstoll', 'wcstoul', 'wcstoull', 'wcstof', 'wcstod', 'wcstold', 'swprintf', 'pthread_join', 'pthread_detach', 'strcoll_l', 'strxfrm_l', 'wcscoll_l', 'toupper_l', 'tolower_l', 'iswspace_l', 'iswprint_l', 'iswcntrl_l', 'iswupper_l', 'iswlower_l', 'iswalpha_l', 'iswdigit_l', 'iswpunct_l', 'iswxdigit_l', 'iswblank_l', 'wcsxfrm_l', 'towupper_l', 'towlower_l', 'catgets', 'catopen', 'catclose'].forEach(function(aborter) { +['pthread_cond_signal', 'pthread_equal', 'wcstol', 'wcstoll', 'wcstoul', 'wcstoull', 'wcstof', 'wcstod', 'wcstold', 'pthread_join', 'pthread_detach', 'catgets', 'catopen', 'catclose'].forEach(function(aborter) { LibraryManager.library[aborter] = function() { throw 'TODO: ' + aborter }; }); diff --git a/src/library_gl.js b/src/library_gl.js index 16ea55313aea5..a4d35aff0d7fc 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -217,6 +217,23 @@ var LibraryGL = { throw 'Invalid format (' + format + ')'; } break; + case 0x1403 /* GL_UNSIGNED_SHORT */: + if (format == 0x1902 /* GL_DEPTH_COMPONENT */) { + sizePerPixel = 2; + } else { + throw 'Invalid format (' + format + ')'; + } + break; + case 0x1405 /* GL_UNSIGNED_INT */: + if (format == 0x1902 /* GL_DEPTH_COMPONENT */) { + sizePerPixel = 4; + } else { + throw 'Invalid format (' + format + ')'; + } + break; + case 0x84FA /* UNSIGNED_INT_24_8_WEBGL */: + sizePerPixel = 4; + break; case 0x8363 /* GL_UNSIGNED_SHORT_5_6_5 */: case 0x8033 /* GL_UNSIGNED_SHORT_4_4_4_4 */: case 0x8034 /* GL_UNSIGNED_SHORT_5_5_5_1 */: @@ -244,6 +261,8 @@ var LibraryGL = { pixels = {{{ makeHEAPView('U8', 'pixels', 'pixels+bytes') }}}; } else if (type == 0x1406 /* GL_FLOAT */) { pixels = {{{ makeHEAPView('F32', 'pixels', 'pixels+bytes') }}}; + } else if (type == 0x1405 /* GL_UNSIGNED_INT */ || type == 0x84FA /* UNSIGNED_INT_24_8_WEBGL */) { + pixels = {{{ makeHEAPView('U32', 'pixels', 'pixels+bytes') }}}; } else { pixels = {{{ makeHEAPView('U16', 'pixels', 'pixels+bytes') }}}; } @@ -302,6 +321,18 @@ var LibraryGL = { }, #endif +#if GL_ASSERTIONS + validateGLObjectID: function(objectHandleArray, objectID, callerFunctionName, objectReadableType) { + if (objectID != 0) { + if (objectHandleArray[objectID] === null) { + console.error(callerFunctionName + ' called with an already deleted ' + objectReadableType + ' ID ' + objectID + '!'); + } else if (!objectHandleArray[objectID]) { + console.error(callerFunctionName + ' called with an invalid ' + objectReadableType + ' ID ' + objectID + '!'); + } + } + }, +#endif + initExtensions: function() { if (GL.initExtensions.done) return; GL.initExtensions.done = true; @@ -334,6 +365,10 @@ var LibraryGL = { GL.elementIndexUintExt = Module.ctx.getExtension('OES_element_index_uint'); GL.standardDerivativesExt = Module.ctx.getExtension('OES_standard_derivatives'); + + GL.depthTextureExt = Module.ctx.getExtension("WEBGL_depth_texture") || + Module.ctx.getExtension("MOZ_WEBGL_depth_texture") || + Module.ctx.getExtension("WEBKIT_WEBGL_depth_texture"); } }, @@ -588,6 +623,9 @@ var LibraryGL = { glBindTexture__sig: 'vii', glBindTexture: function(target, texture) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.textures, texture, 'glBindTexture', 'texture'); +#endif Module.ctx.bindTexture(target, texture ? GL.textures[texture] : null); }, @@ -710,6 +748,9 @@ var LibraryGL = { glBindRenderbuffer__sig: 'vii', glBindRenderbuffer: function(target, renderbuffer) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.renderbuffers, renderbuffer, 'glBindRenderbuffer', 'renderbuffer'); +#endif Module.ctx.bindRenderbuffer(target, renderbuffer ? GL.renderbuffers[renderbuffer] : null); }, @@ -727,6 +768,9 @@ var LibraryGL = { glGetUniformfv__sig: 'viii', glGetUniformfv: function(program, location, params) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glGetUniformfv', 'program'); +#endif var data = Module.ctx.getUniform(GL.programs[program], GL.uniforms[location]); if (typeof data == 'number') { {{{ makeSetValue('params', '0', 'data', 'float') }}}; @@ -739,6 +783,9 @@ var LibraryGL = { glGetUniformiv__sig: 'viii', glGetUniformiv: function(program, location, params) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glGetUniformiv', 'program'); +#endif var data = Module.ctx.getUniform(GL.programs[program], GL.uniforms[location]); if (typeof data == 'number' || typeof data == 'boolean') { {{{ makeSetValue('params', '0', 'data', 'i32') }}}; @@ -751,6 +798,9 @@ var LibraryGL = { glGetUniformLocation__sig: 'iii', glGetUniformLocation: function(program, name) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glGetUniformLocation', 'program'); +#endif name = Pointer_stringify(name); var ptable = GL.uniformTable[program]; if (!ptable) ptable = GL.uniformTable[program] = {}; @@ -810,6 +860,9 @@ var LibraryGL = { glGetActiveUniform__sig: 'viiiiiii', glGetActiveUniform: function(program, index, bufSize, length, size, type, name) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glGetActiveUniform', 'program'); +#endif program = GL.programs[program]; var info = Module.ctx.getActiveUniform(program, index); @@ -1018,6 +1071,9 @@ var LibraryGL = { glBindBuffer__sig: 'vii', glBindBuffer: function(target, buffer) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.buffers, buffer, 'glBindBuffer', 'buffer'); +#endif var bufferObj = buffer ? GL.buffers[buffer] : null; if (target == Module.ctx.ARRAY_BUFFER) { @@ -1062,6 +1118,9 @@ var LibraryGL = { glGetActiveAttrib__sig: 'viiiiiii', glGetActiveAttrib: function(program, index, bufSize, length, size, type, name) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glGetActiveAttrib', 'program'); +#endif program = GL.programs[program]; var info = Module.ctx.getActiveAttrib(program, index); @@ -1094,6 +1153,9 @@ var LibraryGL = { glGetAttachedShaders__sig: 'viiii', glGetAttachedShaders: function(program, maxCount, count, shaders) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glGetAttachedShaders', 'program'); +#endif var result = Module.ctx.getAttachedShaders(GL.programs[program]); var len = result.length; if (len > maxCount) { @@ -1109,12 +1171,18 @@ var LibraryGL = { glShaderSource__sig: 'viiii', glShaderSource: function(shader, count, string, length) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.shaders, shader, 'glShaderSource', 'shader'); +#endif var source = GL.getSource(shader, count, string, length); Module.ctx.shaderSource(GL.shaders[shader], source); }, glGetShaderSource__sig: 'viiii', glGetShaderSource: function(shader, bufSize, length, source) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.shaders, shader, 'glGetShaderSource', 'shader'); +#endif var result = Module.ctx.getShaderSource(GL.shaders[shader]); result = result.slice(0, Math.max(0, bufSize - 1)); writeStringToMemory(result, source); @@ -1125,11 +1193,17 @@ var LibraryGL = { glCompileShader__sig: 'vi', glCompileShader: function(shader) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.shaders, shader, 'glCompileShader', 'shader'); +#endif Module.ctx.compileShader(GL.shaders[shader]); }, glGetShaderInfoLog__sig: 'viiii', glGetShaderInfoLog: function(shader, maxLength, length, infoLog) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.shaders, shader, 'glGetShaderInfoLog', 'shader'); +#endif var log = Module.ctx.getShaderInfoLog(GL.shaders[shader]); // Work around a bug in Chromium which causes getShaderInfoLog to return null if (!log) { @@ -1144,6 +1218,9 @@ var LibraryGL = { glGetShaderiv__sig: 'viii', glGetShaderiv : function(shader, pname, p) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.shaders, shader, 'glGetShaderiv', 'shader'); +#endif if (pname == 0x8B84) { // GL_INFO_LOG_LENGTH {{{ makeSetValue('p', '0', 'Module.ctx.getShaderInfoLog(GL.shaders[shader]).length + 1', 'i32') }}}; } else { @@ -1153,6 +1230,9 @@ var LibraryGL = { glGetProgramiv__sig: 'viii', glGetProgramiv : function(program, pname, p) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glGetProgramiv', 'program'); +#endif if (pname == 0x8B84) { // GL_INFO_LOG_LENGTH {{{ makeSetValue('p', '0', 'Module.ctx.getProgramInfoLog(GL.programs[program]).length + 1', 'i32') }}}; } else { @@ -1187,12 +1267,20 @@ var LibraryGL = { glAttachShader__sig: 'vii', glAttachShader: function(program, shader) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glAttachShader', 'program'); + GL.validateGLObjectID(GL.shaders, shader, 'glAttachShader', 'shader'); +#endif Module.ctx.attachShader(GL.programs[program], GL.shaders[shader]); }, glDetachShader__sig: 'vii', glDetachShader: function(program, shader) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glDetachShader', 'program'); + GL.validateGLObjectID(GL.shaders, shader, 'glDetachShader', 'shader'); +#endif Module.ctx.detachShader(GL.programs[program], GL.shaders[shader]); }, @@ -1206,12 +1294,18 @@ var LibraryGL = { glLinkProgram__sig: 'vi', glLinkProgram: function(program) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glLinkProgram', 'program'); +#endif Module.ctx.linkProgram(GL.programs[program]); GL.uniformTable[program] = {}; // uniforms no longer keep the same names after linking }, glGetProgramInfoLog__sig: 'viiii', glGetProgramInfoLog: function(program, maxLength, length, infoLog) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glGetProgramInfoLog', 'program'); +#endif var log = Module.ctx.getProgramInfoLog(GL.programs[program]); // Work around a bug in Chromium which causes getProgramInfoLog to return null if (!log) { @@ -1226,11 +1320,17 @@ var LibraryGL = { glUseProgram__sig: 'vi', glUseProgram: function(program) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glUseProgram', 'program'); +#endif Module.ctx.useProgram(program ? GL.programs[program] : null); }, glValidateProgram__sig: 'vi', glValidateProgram: function(program) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glValidateProgram', 'program'); +#endif Module.ctx.validateProgram(GL.programs[program]); }, @@ -1243,12 +1343,18 @@ var LibraryGL = { glBindAttribLocation__sig: 'viii', glBindAttribLocation: function(program, index, name) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glBindAttribLocation', 'program'); +#endif name = Pointer_stringify(name); Module.ctx.bindAttribLocation(GL.programs[program], index, name); }, glBindFramebuffer__sig: 'vii', glBindFramebuffer: function(target, framebuffer) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.framebuffers, framebuffer, 'glBindFramebuffer', 'framebuffer'); +#endif Module.ctx.bindFramebuffer(target, framebuffer ? GL.framebuffers[framebuffer] : null); }, @@ -1276,12 +1382,18 @@ var LibraryGL = { glFramebufferRenderbuffer__sig: 'viiii', glFramebufferRenderbuffer: function(target, attachment, renderbuffertarget, renderbuffer) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.renderbuffers, renderbuffer, 'glFramebufferRenderbuffer', 'renderbuffer'); +#endif Module.ctx.framebufferRenderbuffer(target, attachment, renderbuffertarget, GL.renderbuffers[renderbuffer]); }, glFramebufferTexture2D__sig: 'viiiii', glFramebufferTexture2D: function(target, attachment, textarget, texture, level) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.textures, texture, 'glFramebufferTexture2D', 'texture'); +#endif Module.ctx.framebufferTexture2D(target, attachment, textarget, GL.textures[texture], level); }, diff --git a/src/library_sdl.js b/src/library_sdl.js index 116bf547c4ec9..63e8b2b128474 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -704,7 +704,7 @@ var LibrarySDL = { // since the browser engine handles that for us. Therefore, in JS we just // maintain a list of channels and return IDs for them to the SDL consumer. allocateChannels: function(num) { // called from Mix_AllocateChannels and init - if (SDL.numChannels && SDL.numChannels >= num) return; + if (SDL.numChannels && SDL.numChannels >= num && num != 0) return; SDL.numChannels = num; SDL.channels = []; for (var i = 0; i < num; i++) { @@ -1131,7 +1131,10 @@ var LibrarySDL = { } else { dr = { x: 0, y: 0, w: -1, h: -1 }; } + var oldAlpha = dstData.ctx.globalAlpha; + dstData.ctx.globalAlpha = srcData.alpha/255; dstData.ctx.drawImage(srcData.canvas, sr.x, sr.y, sr.w, sr.h, dr.x, dr.y, sr.w, sr.h); + dstData.ctx.globalAlpha = oldAlpha; if (dst != SDL.screen) { // XXX As in IMG_Load, for compatibility we write out |pixels| console.log('WARNING: copying canvas data to memory for compatibility'); @@ -1454,60 +1457,240 @@ var LibrarySDL = { // SDL_Audio - // TODO fix SDL_OpenAudio, and add some tests for it. It's currently broken. SDL_OpenAudio: function(desired, obtained) { - SDL.allocateChannels(32); - - SDL.audio = { - freq: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.freq', 'i32', 0, 1) }}}, - format: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.format', 'i16', 0, 1) }}}, - channels: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.channels', 'i8', 0, 1) }}}, - samples: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.samples', 'i16', 0, 1) }}}, - callback: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.callback', 'void*', 0, 1) }}}, - userdata: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.userdata', 'void*', 0, 1) }}}, - paused: true, - timer: null - }; - - if (obtained) { - {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.freq', 'SDL.audio.freq', 'i32') }}}; // no good way for us to know if the browser can really handle this - {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.format', 33040, 'i16') }}}; // float, signed, 16-bit - {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.channels', 'SDL.audio.channels', 'i8') }}}; - {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.silence', makeGetValue('desired', 'SDL.structs.AudioSpec.silence', 'i8', 0, 1), 'i8') }}}; // unclear if browsers can provide this - {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.samples', 'SDL.audio.samples', 'i16') }}}; - {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.callback', 'SDL.audio.callback', '*') }}}; - {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.userdata', 'SDL.audio.userdata', '*') }}}; - } - - var totalSamples = SDL.audio.samples*SDL.audio.channels; - SDL.audio.bufferSize = totalSamples*2; // hardcoded 16-bit audio - SDL.audio.buffer = _malloc(SDL.audio.bufferSize); - SDL.audio.caller = function() { - Runtime.dynCall('viii', SDL.audio.callback, [SDL.audio.userdata, SDL.audio.buffer, SDL.audio.bufferSize]); - SDL.audio.pushAudio(SDL.audio.buffer, SDL.audio.bufferSize); - }; - // Mozilla Audio API. TODO: Other audio APIs try { - SDL.audio.mozOutput = new Audio(); - SDL.audio.mozOutput['mozSetup'](SDL.audio.channels, SDL.audio.freq); // use string attributes on mozOutput for closure compiler - SDL.audio.mozBuffer = new Float32Array(totalSamples); - SDL.audio.pushAudio = function(ptr, size) { - var mozBuffer = SDL.audio.mozBuffer; - for (var i = 0; i < totalSamples; i++) { - mozBuffer[i] = ({{{ makeGetValue('ptr', 'i*2', 'i16', 0, 0) }}}) / 0x8000; // hardcoded 16-bit audio, signed (TODO: reSign if not ta2?) + SDL.audio = { + freq: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.freq', 'i32', 0, 1) }}}, + format: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.format', 'i16', 0, 1) }}}, + channels: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.channels', 'i8', 0, 1) }}}, + samples: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.samples', 'i16', 0, 1) }}}, // Samples in the CB buffer per single sound channel. + callback: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.callback', 'void*', 0, 1) }}}, + userdata: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.userdata', 'void*', 0, 1) }}}, + paused: true, + timer: null + }; + // The .silence field tells the constant sample value that corresponds to the safe un-skewed silence value for the wave data. + if (SDL.audio.format == 0x0008 /*AUDIO_U8*/) { + SDL.audio.silence = 128; // Audio ranges in [0, 255], so silence is half-way in between. + } else if (SDL.audio.format == 0x8010 /*AUDIO_S16LSB*/) { + SDL.audio.silence = 0; // Signed data in range [-32768, 32767], silence is 0. + } else { + throw 'Invalid SDL audio format ' + SDL.audio.format + '!'; + } + // Round the desired audio frequency up to the next 'common' frequency value. + // Web Audio API spec states 'An implementation must support sample-rates in at least the range 22050 to 96000.' + if (SDL.audio.freq <= 0) { + throw 'Unsupported sound frequency ' + SDL.audio.freq + '!'; + } else if (SDL.audio.freq <= 22050) { + SDL.audio.freq = 22050; // Take it safe and clamp everything lower than 22kHz to that. + } else if (SDL.audio.freq <= 32000) { + SDL.audio.freq = 32000; + } else if (SDL.audio.freq <= 44100) { + SDL.audio.freq = 44100; + } else if (SDL.audio.freq <= 48000) { + SDL.audio.freq = 48000; + } else if (SDL.audio.freq <= 96000) { + SDL.audio.freq = 96000; + } else { + throw 'Unsupported sound frequency ' + SDL.audio.freq + '!'; + } + if (SDL.audio.channels == 0) { + SDL.audio.channels = 1; // In SDL both 0 and 1 mean mono. + } else if (SDL.audio.channels < 0 || SDL.audio.channels > 32) { + throw 'Unsupported number of audio channels for SDL audio: ' + SDL.audio.channels + '!'; + } else if (SDL.audio.channels != 1 && SDL.audio.channels != 2) { // Unsure what SDL audio spec supports. Web Audio spec supports up to 32 channels. + console.log('Warning: Using untested number of audio channels ' + SDL.audio.channels); + } + if (SDL.audio.samples < 1024 || SDL.audio.samples > 524288 /* arbitrary cap */) { + throw 'Unsupported audio callback buffer size ' + SDL.audio.samples + '!'; + } else if ((SDL.audio.samples & (SDL.audio.samples-1)) != 0) { + throw 'Audio callback buffer size ' + SDL.audio.samples + ' must be a power-of-two!'; + } + + var totalSamples = SDL.audio.samples*SDL.audio.channels; + SDL.audio.bytesPerSample = (SDL.audio.format == 0x0008 /*AUDIO_U8*/ || SDL.audio.format == 0x8008 /*AUDIO_S8*/) ? 1 : 2; + SDL.audio.bufferSize = totalSamples*SDL.audio.bytesPerSample; + SDL.audio.buffer = _malloc(SDL.audio.bufferSize); + + // Create a callback function that will be routinely called to ask more audio data from the user application. + SDL.audio.caller = function() { + if (!SDL.audio) { + return; + } + Runtime.dynCall('viii', SDL.audio.callback, [SDL.audio.userdata, SDL.audio.buffer, SDL.audio.bufferSize]); + SDL.audio.pushAudio(SDL.audio.buffer, SDL.audio.bufferSize); + }; + + SDL.audio.audioOutput = new Audio(); + // As a workaround use Mozilla Audio Data API on Firefox until it ships with Web Audio and sound quality issues are fixed. + if (typeof(SDL.audio.audioOutput['mozSetup'])==='function') { + SDL.audio.audioOutput['mozSetup'](SDL.audio.channels, SDL.audio.freq); // use string attributes on mozOutput for closure compiler + SDL.audio.mozBuffer = new Float32Array(totalSamples); + SDL.audio.nextPlayTime = 0; + SDL.audio.pushAudio = function(ptr, size) { + var mozBuffer = SDL.audio.mozBuffer; + // The input audio data for SDL audio is either 8-bit or 16-bit interleaved across channels, output for Mozilla Audio Data API + // needs to be Float32 interleaved, so perform a sample conversion. + if (SDL.audio.format == 0x8010 /*AUDIO_S16LSB*/) { + for (var i = 0; i < totalSamples; i++) { + mozBuffer[i] = ({{{ makeGetValue('ptr', 'i*2', 'i16', 0, 0) }}}) / 0x8000; + } + } else if (SDL.audio.format == 0x0008 /*AUDIO_U8*/) { + for (var i = 0; i < totalSamples; i++) { + var v = ({{{ makeGetValue('ptr', 'i', 'i8', 0, 0) }}}); + mozBuffer[i] = ((v >= 0) ? v-128 : v+128) /128; + } + } + // Submit the audio data to audio device. + SDL.audio.audioOutput['mozWriteAudio'](mozBuffer); + + // Compute when the next audio callback should be called. + var curtime = Date.now() / 1000.0 - SDL.audio.startTime; + if (curtime > SDL.audio.nextPlayTime && SDL.audio.nextPlayTime != 0) { + console.log('warning: Audio callback had starved sending audio by ' + (curtime - SDL.audio.nextPlayTime) + ' seconds.'); + } + var playtime = Math.max(curtime, SDL.audio.nextPlayTime); + var buffer_duration = SDL.audio.samples / SDL.audio.freq; + SDL.audio.nextPlayTime = playtime + buffer_duration; + // Schedule the next audio callback call. + SDL.audio.timer = Browser.safeSetTimeout(SDL.audio.caller, 1000.0 * (playtime-curtime)); + } + } else { + // Initialize Web Audio API if we haven't done so yet. Note: Only initialize Web Audio context ever once on the web page, + // since initializing multiple times fails on Chrome saying 'audio resources have been exhausted'. + if (!SDL.audioContext) { + if (typeof(AudioContext) === 'function') { + SDL.audioContext = new AudioContext(); + } else if (typeof(webkitAudioContext) === 'function') { + SDL.audioContext = new webkitAudioContext(); + } else { + throw 'Web Audio API is not available!'; + } + } + SDL.audio.soundSource = new Array(); // Use an array of sound sources as a ring buffer to queue blocks of synthesized audio to Web Audio API. + SDL.audio.nextSoundSource = 0; // Index of the next sound buffer in the ring buffer queue to play. + SDL.audio.nextPlayTime = 0; // Time in seconds when the next audio block is due to start. + + // The pushAudio function with a new audio buffer whenever there is new audio data to schedule to be played back on the device. + SDL.audio.pushAudio=function(ptr,sizeBytes) { + try { + --SDL.audio.numAudioTimersPending; + + var sizeSamples = sizeBytes / SDL.audio.bytesPerSample; // How many samples fit in the callback buffer? + var sizeSamplesPerChannel = sizeSamples / SDL.audio.channels; // How many samples per a single channel fit in the cb buffer? + if (sizeSamplesPerChannel != SDL.audio.samples) { + throw 'Received mismatching audio buffer size!'; + } + // Allocate new sound buffer to be played. + var source = SDL.audioContext['createBufferSource'](); + if (SDL.audio.soundSource[SDL.audio.nextSoundSource]) { + SDL.audio.soundSource[SDL.audio.nextSoundSource]['disconnect'](); // Explicitly disconnect old source, since we know it shouldn't be running anymore. + } + SDL.audio.soundSource[SDL.audio.nextSoundSource] = source; + var soundBuffer = SDL.audioContext['createBuffer'](SDL.audio.channels,sizeSamplesPerChannel,SDL.audio.freq); + SDL.audio.soundSource[SDL.audio.nextSoundSource]['connect'](SDL.audioContext['destination']); + + // The input audio data is interleaved across the channels, i.e. [L, R, L, R, L, R, ...] and is either 8-bit or 16-bit as + // supported by the SDL API. The output audio wave data for Web Audio API must be in planar buffers of [-1,1]-normalized Float32 data, + // so perform a buffer conversion for the data. + var numChannels = SDL.audio.channels; + for(var i = 0; i < numChannels; ++i) { + var channelData = soundBuffer['getChannelData'](i); + if (channelData.length != sizeSamplesPerChannel) { + throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + sizeSamplesPerChannel + ' samples!'; + } + if (SDL.audio.format == 0x8010 /*AUDIO_S16LSB*/) { + for(var j = 0; j < sizeSamplesPerChannel; ++j) { + channelData[j] = ({{{ makeGetValue('ptr', '(j*numChannels + i)*2', 'i16', 0, 0) }}}) / 0x8000; + } + } else if (SDL.audio.format == 0x0008 /*AUDIO_U8*/) { + for(var j = 0; j < sizeSamplesPerChannel; ++j) { + var v = ({{{ makeGetValue('ptr', 'j*numChannels + i', 'i8', 0, 0) }}}); + channelData[j] = ((v >= 0) ? v-128 : v+128) /128; + } + } + } + // Workaround https://bugzilla.mozilla.org/show_bug.cgi?id=883675 by setting the buffer only after filling. The order is important here! + source['buffer'] = soundBuffer; + + // Schedule the generated sample buffer to be played out at the correct time right after the previously scheduled + // sample buffer has finished. + var curtime = SDL.audioContext['currentTime']; +// if (curtime > SDL.audio.nextPlayTime && SDL.audio.nextPlayTime != 0) { +// console.log('warning: Audio callback had starved sending audio by ' + (curtime - SDL.audio.nextPlayTime) + ' seconds.'); +// } + var playtime = Math.max(curtime, SDL.audio.nextPlayTime); + SDL.audio.soundSource[SDL.audio.nextSoundSource]['start'](playtime); + var buffer_duration = sizeSamplesPerChannel / SDL.audio.freq; + SDL.audio.nextPlayTime = playtime + buffer_duration; + SDL.audio.nextSoundSource = (SDL.audio.nextSoundSource + 1) % 4; + var secsUntilNextCall = playtime-curtime; + + // Queue the next audio frame push to be performed when the previously queued buffer has finished playing. + if (SDL.audio.numAudioTimersPending == 0) { + var preemptBufferFeedMSecs = buffer_duration/2.0; + SDL.audio.timer = Browser.safeSetTimeout(SDL.audio.caller, Math.max(0.0, 1000.0*secsUntilNextCall-preemptBufferFeedMSecs)); + ++SDL.audio.numAudioTimersPending; + } + + // If we are risking starving, immediately queue an extra second buffer. + if (secsUntilNextCall <= buffer_duration && SDL.audio.numAudioTimersPending <= 1) { + ++SDL.audio.numAudioTimersPending; + Browser.safeSetTimeout(SDL.audio.caller, 1.0); + } + } catch(e) { + console.log('Web Audio API error playing back audio: ' + e.toString()); + } } - SDL.audio.mozOutput['mozWriteAudio'](mozBuffer); } + + if (obtained) { + // Report back the initialized audio parameters. + {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.freq', 'SDL.audio.freq', 'i32') }}}; + {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.format', 'SDL.audio.format', 'i16') }}}; + {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.channels', 'SDL.audio.channels', 'i8') }}}; + {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.silence', 'SDL.audio.silence', 'i8') }}}; + {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.samples', 'SDL.audio.samples', 'i16') }}}; + {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.callback', 'SDL.audio.callback', '*') }}}; + {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.userdata', 'SDL.audio.userdata', '*') }}}; + } + SDL.allocateChannels(32); + } catch(e) { + console.log('Initializing SDL audio threw an exception: "' + e.toString() + '"! Continuing without audio.'); SDL.audio = null; + SDL.allocateChannels(0); + if (obtained) { + {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.freq', 0, 'i32') }}}; + {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.format', 0, 'i16') }}}; + {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.channels', 0, 'i8') }}}; + {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.silence', 0, 'i8') }}}; + {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.samples', 0, 'i16') }}}; + {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.callback', 0, '*') }}}; + {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.userdata', 0, '*') }}}; + } + } + if (!SDL.audio) { + return -1; } - if (!SDL.audio) return -1; return 0; }, SDL_PauseAudio: function(pauseOn) { - if (SDL.audio.paused !== pauseOn) { - SDL.audio.timer = pauseOn ? SDL.audio.timer && clearInterval(SDL.audio.timer) : Browser.safeSetInterval(SDL.audio.caller, 1/35); + if (!SDL.audio) { + return; + } + if (pauseOn) { + if (SDL.audio.timer !== undefined) { + clearTimeout(SDL.audio.timer); + SDL.audio.numAudioTimersPending = 0; + SDL.audio.timer = undefined; + } + } else if (!SDL.audio.timer) { + // Start the audio playback timer callback loop. + SDL.audio.numAudioTimersPending = 1; + SDL.audio.timer = Browser.safeSetTimeout(SDL.audio.caller, 1); + SDL.audio.startTime = Date.now() / 1000.0; // Only used for Mozilla Audio Data API. Not needed for Web Audio API. } SDL.audio.paused = pauseOn; }, @@ -1515,9 +1698,18 @@ var LibrarySDL = { SDL_CloseAudio__deps: ['SDL_PauseAudio', 'free'], SDL_CloseAudio: function() { if (SDL.audio) { + try{ + for(var i = 0; i < SDL.audio.soundSource.length; ++i) { + if (!(typeof(SDL.audio.soundSource[i]==='undefined'))) { + SDL.audio.soundSource[i].stop(0); + } + } + } catch(e) {} + SDL.audio.soundSource = null; _SDL_PauseAudio(1); _free(SDL.audio.buffer); SDL.audio = null; + SDL.allocateChannels(0); } }, diff --git a/src/parseTools.js b/src/parseTools.js index 90c5acab265a8..8ce83adf7b229 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1196,7 +1196,7 @@ function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSa var typeData = Types.types[type]; var ret = []; for (var i = 0; i < typeData.fields.length; i++) { - ret.push('f' + i + ': ' + makeGetValue(ptr, pos + typeData.flatIndexes[i], typeData.fields[i], noNeedFirst, unsigned)); + ret.push('f' + i + ': ' + makeGetValue(ptr, pos + typeData.flatIndexes[i], typeData.fields[i], noNeedFirst, unsigned, 0, 0, noSafe)); } return '{ ' + ret.join(', ') + ' }'; } @@ -1204,8 +1204,8 @@ function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSa // In double mode 1, in x86 we always assume unaligned because we can't trust that; otherwise in le32 // we need this code path if we are not fully aligned. if (DOUBLE_MODE == 1 && USE_TYPED_ARRAYS == 2 && type == 'double' && (TARGET_X86 || align < 8)) { - return '(' + makeSetTempDouble(0, 'i32', makeGetValue(ptr, pos, 'i32', noNeedFirst, unsigned, ignore, align)) + ',' + - makeSetTempDouble(1, 'i32', makeGetValue(ptr, getFastValue(pos, '+', Runtime.getNativeTypeSize('i32')), 'i32', noNeedFirst, unsigned, ignore, align)) + ',' + + return '(' + makeSetTempDouble(0, 'i32', makeGetValue(ptr, pos, 'i32', noNeedFirst, unsigned, ignore, align, noSafe)) + ',' + + makeSetTempDouble(1, 'i32', makeGetValue(ptr, getFastValue(pos, '+', Runtime.getNativeTypeSize('i32')), 'i32', noNeedFirst, unsigned, ignore, align, noSafe)) + ',' + makeGetTempDouble(0, 'double') + ')'; } @@ -1218,12 +1218,12 @@ function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSa if (isIntImplemented(type)) { if (bytes == 4 && align == 2) { // Special case that we can optimize - ret += makeGetValue(ptr, pos, 'i16', noNeedFirst, 2, ignore) + '|' + - '(' + makeGetValue(ptr, getFastValue(pos, '+', 2), 'i16', noNeedFirst, 2, ignore) + '<<16)'; + ret += makeGetValue(ptr, pos, 'i16', noNeedFirst, 2, ignore, 2, noSafe) + '|' + + '(' + makeGetValue(ptr, getFastValue(pos, '+', 2), 'i16', noNeedFirst, 2, ignore, 2, noSafe) + '<<16)'; } else { // XXX we cannot truly handle > 4... (in x86) ret = ''; for (var i = 0; i < bytes; i++) { - ret += '(' + makeGetValue(ptr, getFastValue(pos, '+', i), 'i8', noNeedFirst, 1, ignore) + (i > 0 ? '<<' + (8*i) : '') + ')'; + ret += '(' + makeGetValue(ptr, getFastValue(pos, '+', i), 'i8', noNeedFirst, 1, ignore, 1, noSafe) + (i > 0 ? '<<' + (8*i) : '') + ')'; if (i < bytes-1) ret += '|'; } ret = '(' + makeSignOp(ret, type, unsigned ? 'un' : 're', true); @@ -1302,7 +1302,7 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, value = range(typeData.fields.length).map(function(i) { return value + '.f' + i }); } for (var i = 0; i < typeData.fields.length; i++) { - ret.push(makeSetValue(ptr, getFastValue(pos, '+', typeData.flatIndexes[i]), value[i], typeData.fields[i], noNeedFirst)); + ret.push(makeSetValue(ptr, getFastValue(pos, '+', typeData.flatIndexes[i]), value[i], typeData.fields[i], noNeedFirst, 0, 0, noSafe)); } return ret.join('; '); } @@ -1329,17 +1329,17 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, if (bytes == 4 && align == 2) { // Special case that we can optimize ret += 'tempBigInt=' + value + sep; - ret += makeSetValue(ptr, pos, 'tempBigInt&0xffff', 'i16', noNeedFirst, ignore, 2) + sep; - ret += makeSetValue(ptr, getFastValue(pos, '+', 2), 'tempBigInt>>16', 'i16', noNeedFirst, ignore, 2); + ret += makeSetValue(ptr, pos, 'tempBigInt&0xffff', 'i16', noNeedFirst, ignore, 2, noSafe) + sep; + ret += makeSetValue(ptr, getFastValue(pos, '+', 2), 'tempBigInt>>16', 'i16', noNeedFirst, ignore, 2, noSafe); } else { ret += 'tempBigInt=' + value + sep; for (var i = 0; i < bytes; i++) { - ret += makeSetValue(ptr, getFastValue(pos, '+', i), 'tempBigInt&0xff', 'i8', noNeedFirst, ignore, 1); + ret += makeSetValue(ptr, getFastValue(pos, '+', i), 'tempBigInt&0xff', 'i8', noNeedFirst, ignore, 1, noSafe); if (i < bytes-1) ret += sep + 'tempBigInt = tempBigInt>>8' + sep; } } } else { - ret += makeSetValue('tempDoublePtr', 0, value, type, noNeedFirst, ignore, 8, null, null, true) + sep; + ret += makeSetValue('tempDoublePtr', 0, value, type, noNeedFirst, ignore, 8, noSafe, null, true) + sep; ret += makeCopyValues(getFastValue(ptr, '+', pos), 'tempDoublePtr', Runtime.getNativeTypeSize(type), type, null, align, sep); } return ret; @@ -1499,7 +1499,9 @@ function getFastValue(a, op, b, type) { } if (op in MUL_DIV) { if (op == '*') { - if (a == 0 || b == 0) { + // We can't eliminate where a or b are 0 as that would break things for creating + // a negative 0. + if ((a == 0 || b == 0) && !(type in Runtime.FLOAT_TYPES)) { return '0'; } else if (a == 1) { return b; @@ -1520,8 +1522,8 @@ function getFastValue(a, op, b, type) { } return '(Math.imul(' + a + ',' + b + ')|0)'; } - } else { - if (a == '0') { + } else { // div + if (a == '0' && !(type in Runtime.FLOAT_TYPES)) { // careful on floats, since 0*NaN is not 0 return '0'; } else if (b == 1) { return a; @@ -1758,7 +1760,7 @@ function checkBitcast(item) { } else { warnOnce('Casting a function pointer type to a potentially incompatible one (use -s VERBOSE=1 to see more)'); } - warnOnce('See https://github.com/kripken/emscripten/wiki/CodeGuidlinesAndLimitations#function-pointer-issues for more information on dangerous function pointer casts'); + warnOnce('See https://github.com/kripken/emscripten/wiki/CodeGuidelinesAndLimitations#function-pointer-issues for more information on dangerous function pointer casts'); if (ASM_JS) warnOnce('Incompatible function pointer casts are very dangerous with ASM_JS=1, you should investigate and correct these'); } if (oldCount != newCount && oldCount && newCount) showWarning(); diff --git a/src/preamble.js b/src/preamble.js index acff665f8e629..183fd0c8340c6 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -299,11 +299,10 @@ function ccallFunc(func, returnType, argTypes, args) { function toC(value, type) { if (type == 'string') { if (value === null || value === undefined || value === 0) return 0; // null string - if (!stack) stack = Runtime.stackSave(); - var ret = Runtime.stackAlloc(value.length+1); - writeStringToMemory(value, ret); - return ret; - } else if (type == 'array') { + value = intArrayFromString(value); + type = 'array'; + } + if (type == 'array') { if (!stack) stack = Runtime.stackSave(); var ret = Runtime.stackAlloc(value.length); writeArrayToMemory(value, ret); @@ -717,7 +716,7 @@ var FAST_MEMORY = Module['FAST_MEMORY'] || {{{ FAST_MEMORY }}}; // Initialize the runtime's memory #if USE_TYPED_ARRAYS // check for full engine support (use string 'subarray' to avoid closure compiler confusion) -assert(!!Int32Array && !!Float64Array && !!(new Int32Array(1)['subarray']) && !!(new Int32Array(1)['set']), +assert(typeof Int32Array !== 'undefined' && typeof Float64Array !== 'undefined' && !!(new Int32Array(1)['subarray']) && !!(new Int32Array(1)['set']), 'Cannot fallback to non-typed array case: Code is too specialized'); #if USE_TYPED_ARRAYS == 1 diff --git a/src/settings.js b/src/settings.js index 15bca4db5251c..0daafa352e220 100644 --- a/src/settings.js +++ b/src/settings.js @@ -202,6 +202,7 @@ var SOCKET_WEBRTC = 0; // Select socket backend, either webrtc or websockets. var OPENAL_DEBUG = 0; // Print out debugging information from our OpenAL implementation. +var GL_ASSERTIONS = 0; // Adds extra checks for error situations in the GL library. Can impact performance. var GL_DEBUG = 0; // Print out all calls into WebGL. As with LIBRARY_DEBUG, you can set a runtime // option, in this case GL.debug. var GL_TESTING = 0; // When enabled, sets preserveDrawingBuffer in the context, to allow tests to work (but adds overhead) diff --git a/system/include/libc/assert.h b/system/include/libc/assert.h index ab745db18022a..c64d3e52db15e 100644 --- a/system/include/libc/assert.h +++ b/system/include/libc/assert.h @@ -12,7 +12,7 @@ extern "C" { #endif -void __assert_fail (const char *, const char *, int, const char *); +_Noreturn void __assert_fail (const char *, const char *, int, const char *); #ifdef __cplusplus } diff --git a/system/lib/libc/musl/readme.txt b/system/lib/libc/musl/readme.txt index 16c584237c538..9ca04036025a7 100644 --- a/system/lib/libc/musl/readme.txt +++ b/system/lib/libc/musl/readme.txt @@ -6,3 +6,4 @@ Differences from upstream musl include: * various 64 bit types are 32 bit instead including off_t, ino_t, dev_t, blkcnt_t, fsblkcnt_t, fsfilcnt_t, rlim_t. * We don't define _POSIX_SHARED_MEMORY_OBJECTS. +* We flag __assert_fail as _Noreturn. diff --git a/system/lib/libc/musl/src/internal/locale_impl.h b/system/lib/libc/musl/src/internal/locale_impl.h new file mode 100644 index 0000000000000..c268124fabf24 --- /dev/null +++ b/system/lib/libc/musl/src/internal/locale_impl.h @@ -0,0 +1,5 @@ +#include <locale.h> + +struct __locale { + int dummy; +}; diff --git a/system/lib/libc/musl/src/internal/stdio_impl.h b/system/lib/libc/musl/src/internal/stdio_impl.h new file mode 100644 index 0000000000000..2083b2fe427d6 --- /dev/null +++ b/system/lib/libc/musl/src/internal/stdio_impl.h @@ -0,0 +1,92 @@ +#ifndef _STDIO_IMPL_H +#define _STDIO_IMPL_H + +#include <stdio.h> +#include "syscall.h" +#include "libc.h" + +#define UNGET 8 + +#define FFINALLOCK(f) ((f)->lock>=0 ? __lockfile((f)) : 0) +#define FLOCK(f) int __need_unlock = ((f)->lock>=0 ? __lockfile((f)) : 0) +#define FUNLOCK(f) if (__need_unlock) __unlockfile((f)); else + +#define F_PERM 1 +#define F_NORD 4 +#define F_NOWR 8 +#define F_EOF 16 +#define F_ERR 32 +#define F_SVB 64 + +struct _IO_FILE { + unsigned flags; + unsigned char *rpos, *rend; + int (*close)(FILE *); + unsigned char *wend, *wpos; + unsigned char *mustbezero_1; + unsigned char *wbase; + size_t (*read)(FILE *, unsigned char *, size_t); + size_t (*write)(FILE *, const unsigned char *, size_t); + off_t (*seek)(FILE *, off_t, int); + unsigned char *buf; + size_t buf_size; + FILE *prev, *next; + int fd; + int pipe_pid; + long lockcount; + short dummy3; + signed char mode; + signed char lbf; + int lock; + int waiters; + void *cookie; + off_t off; + char *getln_buf; + void *mustbezero_2; + unsigned char *shend; + off_t shlim, shcnt; +}; + +size_t __stdio_read(FILE *, unsigned char *, size_t); +size_t __stdio_write(FILE *, const unsigned char *, size_t); +size_t __stdout_write(FILE *, const unsigned char *, size_t); +off_t __stdio_seek(FILE *, off_t, int); +int __stdio_close(FILE *); + +size_t __string_read(FILE *, unsigned char *, size_t); + +int __toread(FILE *); +int __towrite(FILE *); + +#if defined(__PIC__) && (100*__GNUC__+__GNUC_MINOR__ >= 303) +__attribute__((visibility("protected"))) +#endif +int __overflow(FILE *, int), __uflow(FILE *); + +int __fseeko(FILE *, off_t, int); +int __fseeko_unlocked(FILE *, off_t, int); +off_t __ftello(FILE *); +off_t __ftello_unlocked(FILE *); +size_t __fwritex(const unsigned char *, size_t, FILE *); +int __putc_unlocked(int, FILE *); + +FILE *__fdopen(int, const char *); +int __fmodeflags(const char *); + +#define OFLLOCK() LOCK(libc.ofl_lock) +#define OFLUNLOCK() UNLOCK(libc.ofl_lock) + +#define feof(f) ((f)->flags & F_EOF) +#define ferror(f) ((f)->flags & F_ERR) + +#define getc_unlocked(f) \ + ( ((f)->rpos < (f)->rend) ? *(f)->rpos++ : __uflow((f)) ) + +#define putc_unlocked(c, f) ( ((c)!=(f)->lbf && (f)->wpos<(f)->wend) \ + ? *(f)->wpos++ = (c) : __overflow((f),(c)) ) + +/* Caller-allocated FILE * operations */ +FILE *__fopen_rb_ca(const char *, FILE *, unsigned char *, size_t); +int __fclose_ca(FILE *); + +#endif diff --git a/system/lib/libc/musl/src/locale/iswalnum_l.c b/system/lib/libc/musl/src/locale/iswalnum_l.c new file mode 100644 index 0000000000000..c888060c4ce55 --- /dev/null +++ b/system/lib/libc/musl/src/locale/iswalnum_l.c @@ -0,0 +1,6 @@ +#include <wctype.h> + +int iswalnum_l(wint_t c, locale_t l) +{ + return iswalnum(c); +} diff --git a/system/lib/libc/musl/src/locale/iswalpha_l.c b/system/lib/libc/musl/src/locale/iswalpha_l.c new file mode 100644 index 0000000000000..cd2be91e58281 --- /dev/null +++ b/system/lib/libc/musl/src/locale/iswalpha_l.c @@ -0,0 +1,6 @@ +#include <wctype.h> + +int iswalpha_l(wint_t c, locale_t l) +{ + return iswalpha(c); +} diff --git a/system/lib/libc/musl/src/locale/iswblank_l.c b/system/lib/libc/musl/src/locale/iswblank_l.c new file mode 100644 index 0000000000000..f3a2691f581ff --- /dev/null +++ b/system/lib/libc/musl/src/locale/iswblank_l.c @@ -0,0 +1,6 @@ +#include <wctype.h> + +int iswblank_l(wint_t c, locale_t l) +{ + return iswblank(c); +} diff --git a/system/lib/libc/musl/src/locale/iswcntrl_l.c b/system/lib/libc/musl/src/locale/iswcntrl_l.c new file mode 100644 index 0000000000000..7681fe092b6f8 --- /dev/null +++ b/system/lib/libc/musl/src/locale/iswcntrl_l.c @@ -0,0 +1,6 @@ +#include <wctype.h> + +int iswcntrl_l(wint_t c, locale_t l) +{ + return iswcntrl(c); +} diff --git a/system/lib/libc/musl/src/locale/iswctype_l.c b/system/lib/libc/musl/src/locale/iswctype_l.c new file mode 100644 index 0000000000000..13dfb1ed359f4 --- /dev/null +++ b/system/lib/libc/musl/src/locale/iswctype_l.c @@ -0,0 +1,9 @@ +#include <wctype.h> +#include "libc.h" + +int iswctype_l(wint_t c, wctype_t t, locale_t l) +{ + return iswctype(c, t); +} + +weak_alias(iswctype_l, __iswctype_l); diff --git a/system/lib/libc/musl/src/locale/iswdigit_l.c b/system/lib/libc/musl/src/locale/iswdigit_l.c new file mode 100644 index 0000000000000..3de678c24f132 --- /dev/null +++ b/system/lib/libc/musl/src/locale/iswdigit_l.c @@ -0,0 +1,6 @@ +#include <wctype.h> + +int iswdigit_l(wint_t c, locale_t l) +{ + return iswdigit(c); +} diff --git a/system/lib/libc/musl/src/locale/iswgraph_l.c b/system/lib/libc/musl/src/locale/iswgraph_l.c new file mode 100644 index 0000000000000..34df64fc3ba33 --- /dev/null +++ b/system/lib/libc/musl/src/locale/iswgraph_l.c @@ -0,0 +1,6 @@ +#include <wctype.h> + +int iswgraph_l(wint_t c, locale_t l) +{ + return iswgraph(c); +} diff --git a/system/lib/libc/musl/src/locale/iswlower_l.c b/system/lib/libc/musl/src/locale/iswlower_l.c new file mode 100644 index 0000000000000..c52421a043c86 --- /dev/null +++ b/system/lib/libc/musl/src/locale/iswlower_l.c @@ -0,0 +1,6 @@ +#include <wctype.h> + +int iswlower_l(wint_t c, locale_t l) +{ + return iswlower(c); +} diff --git a/system/lib/libc/musl/src/locale/iswprint_l.c b/system/lib/libc/musl/src/locale/iswprint_l.c new file mode 100644 index 0000000000000..73d83ab356333 --- /dev/null +++ b/system/lib/libc/musl/src/locale/iswprint_l.c @@ -0,0 +1,6 @@ +#include <wctype.h> + +int iswprint_l(wint_t c, locale_t l) +{ + return iswprint(c); +} diff --git a/system/lib/libc/musl/src/locale/iswpunct_l.c b/system/lib/libc/musl/src/locale/iswpunct_l.c new file mode 100644 index 0000000000000..831e0e5441963 --- /dev/null +++ b/system/lib/libc/musl/src/locale/iswpunct_l.c @@ -0,0 +1,6 @@ +#include <wctype.h> + +int iswpunct_l(wint_t c, locale_t l) +{ + return iswpunct(c); +} diff --git a/system/lib/libc/musl/src/locale/iswspace_l.c b/system/lib/libc/musl/src/locale/iswspace_l.c new file mode 100644 index 0000000000000..b507e9e3a613c --- /dev/null +++ b/system/lib/libc/musl/src/locale/iswspace_l.c @@ -0,0 +1,6 @@ +#include <wctype.h> + +int iswspace_l(wint_t c, locale_t l) +{ + return iswspace(c); +} diff --git a/system/lib/libc/musl/src/locale/iswupper_l.c b/system/lib/libc/musl/src/locale/iswupper_l.c new file mode 100644 index 0000000000000..fc988ef119b49 --- /dev/null +++ b/system/lib/libc/musl/src/locale/iswupper_l.c @@ -0,0 +1,6 @@ +#include <wctype.h> + +int iswupper_l(wint_t c, locale_t l) +{ + return iswupper(c); +} diff --git a/system/lib/libc/musl/src/locale/iswxdigit_l.c b/system/lib/libc/musl/src/locale/iswxdigit_l.c new file mode 100644 index 0000000000000..9527cf3e1d8c6 --- /dev/null +++ b/system/lib/libc/musl/src/locale/iswxdigit_l.c @@ -0,0 +1,6 @@ +#include <wctype.h> + +int iswxdigit_l(wint_t c, locale_t l) +{ + return iswxdigit(c); +} diff --git a/system/lib/libc/musl/src/locale/strfmon.c b/system/lib/libc/musl/src/locale/strfmon.c new file mode 100644 index 0000000000000..f510d9a42f748 --- /dev/null +++ b/system/lib/libc/musl/src/locale/strfmon.c @@ -0,0 +1,101 @@ +#include <stdio.h> +#include <ctype.h> +#include <stdarg.h> +#include <monetary.h> +#include <errno.h> +#include <stdarg.h> + +static ssize_t vstrfmon_l(char *s, size_t n, locale_t loc, const char *fmt, va_list ap) +{ + size_t l; + double x; + int fill, nogrp, negpar, nosym, left, intl; + int lp, rp, w, fw; + char *s0=s; + for (; n && *fmt; ) { + if (*fmt != '%') { + literal: + *s++ = *fmt++; + n--; + continue; + } + fmt++; + if (*fmt == '%') goto literal; + + fill = ' '; + nogrp = 0; + negpar = 0; + nosym = 0; + left = 0; + for (; ; fmt++) { + switch (*fmt) { + case '=': + fill = *++fmt; + continue; + case '^': + nogrp = 1; + continue; + case '(': + negpar = 1; + case '+': + continue; + case '!': + nosym = 1; + continue; + case '-': + left = 1; + continue; + } + break; + } + + for (fw=0; isdigit(*fmt); fmt++) + fw = 10*fw + (*fmt-'0'); + lp = 0; + rp = 2; + if (*fmt=='#') for (lp=0, fmt++; isdigit(*fmt); fmt++) + lp = 10*lp + (*fmt-'0'); + if (*fmt=='.') for (rp=0, fmt++; isdigit(*fmt); fmt++) + rp = 10*rp + (*fmt-'0'); + + intl = *fmt++ == 'i'; + + w = lp + 1 + rp; + if (!left && fw>w) w = fw; + + x = va_arg(ap, double); + l = snprintf(s, n, "%*.*f", w, rp, x); + if (l >= n) { + errno = E2BIG; + return -1; + } + s += l; + n -= l; + } + return s-s0; +} + +ssize_t strfmon_l(char *restrict s, size_t n, locale_t loc, const char *restrict fmt, ...) +{ + va_list ap; + ssize_t ret; + + va_start(ap, fmt); + ret = vstrfmon_l(s, n, loc, fmt, ap); + va_end(ap); + + return ret; +} + + +ssize_t strfmon(char *restrict s, size_t n, const char *restrict fmt, ...) +{ + va_list ap; + ssize_t ret; + + va_start(ap, fmt); + ret = vstrfmon_l(s, n, 0, fmt, ap); + va_end(ap); + + return ret; +} diff --git a/system/lib/libc/musl/src/locale/strxfrm.c b/system/lib/libc/musl/src/locale/strxfrm.c new file mode 100644 index 0000000000000..32c461939dd42 --- /dev/null +++ b/system/lib/libc/musl/src/locale/strxfrm.c @@ -0,0 +1,18 @@ +#include <string.h> +#include <locale.h> +#include "libc.h" + +/* collate only by code points */ +size_t __strxfrm_l(char *restrict dest, const char *restrict src, size_t n, locale_t loc) +{ + size_t l = strlen(src); + if (n > l) strcpy(dest, src); + return l; +} + +size_t strxfrm(char *restrict dest, const char *restrict src, size_t n) +{ + return __strxfrm_l(dest, src, n, 0); +} + +weak_alias(__strxfrm_l, strxfrm_l); diff --git a/system/lib/libc/musl/src/locale/towctrans_l.c b/system/lib/libc/musl/src/locale/towctrans_l.c new file mode 100644 index 0000000000000..6222058c750cf --- /dev/null +++ b/system/lib/libc/musl/src/locale/towctrans_l.c @@ -0,0 +1,6 @@ +#include <wctype.h> + +wint_t towctrans_l(wint_t c, wctrans_t t, locale_t l) +{ + return towctrans(c, t); +} diff --git a/system/lib/libc/musl/src/locale/towlower_l.c b/system/lib/libc/musl/src/locale/towlower_l.c new file mode 100644 index 0000000000000..aaaea370f81fa --- /dev/null +++ b/system/lib/libc/musl/src/locale/towlower_l.c @@ -0,0 +1,9 @@ +#include <wctype.h> +#include "libc.h" + +wint_t towlower_l(wint_t c, locale_t l) +{ + return towlower(c); +} + +weak_alias(towlower_l, __towlower_l); diff --git a/system/lib/libc/musl/src/locale/towupper_l.c b/system/lib/libc/musl/src/locale/towupper_l.c new file mode 100644 index 0000000000000..ad02a4beab3ec --- /dev/null +++ b/system/lib/libc/musl/src/locale/towupper_l.c @@ -0,0 +1,9 @@ +#include <wctype.h> +#include "libc.h" + +wint_t towupper_l(wint_t c, locale_t l) +{ + return towupper(c); +} + +weak_alias(towupper_l, __towupper_l); diff --git a/system/lib/libc/musl/src/locale/wcscoll.c b/system/lib/libc/musl/src/locale/wcscoll.c new file mode 100644 index 0000000000000..20a60900e0386 --- /dev/null +++ b/system/lib/libc/musl/src/locale/wcscoll.c @@ -0,0 +1,16 @@ +#include <wchar.h> +#include <locale.h> +#include "libc.h" + +/* FIXME: stub */ +int __wcscoll_l(const wchar_t *l, const wchar_t *r, locale_t locale) +{ + return wcscmp(l, r); +} + +int wcscoll(const wchar_t *l, const wchar_t *r) +{ + return __wcscoll_l(l, r, 0); +} + +weak_alias(__wcscoll_l, wcscoll_l); diff --git a/system/lib/libc/musl/src/locale/wcscoll_l.c b/system/lib/libc/musl/src/locale/wcscoll_l.c new file mode 100644 index 0000000000000..f257ec8d98002 --- /dev/null +++ b/system/lib/libc/musl/src/locale/wcscoll_l.c @@ -0,0 +1,6 @@ +#include <wchar.h> + +int wcscoll_l(const wchar_t *l, const wchar_t *r, locale_t locale) +{ + return wcscoll(l, r); +} diff --git a/system/lib/libc/musl/src/locale/wcsxfrm.c b/system/lib/libc/musl/src/locale/wcsxfrm.c new file mode 100644 index 0000000000000..cb79c97e79efd --- /dev/null +++ b/system/lib/libc/musl/src/locale/wcsxfrm.c @@ -0,0 +1,21 @@ +#include <wchar.h> +#include <locale.h> +#include "libc.h" + +/* collate only by code points */ +size_t __wcsxfrm_l(wchar_t *restrict dest, const wchar_t *restrict src, size_t n, locale_t loc) +{ + size_t l = wcslen(src); + if (l >= n) { + wmemcpy(dest, src, n-1); + dest[n-1] = 0; + } else wcscpy(dest, src); + return l; +} + +size_t wcsxfrm(wchar_t *restrict dest, const wchar_t *restrict src, size_t n) +{ + return __wcsxfrm_l(dest, src, n, 0); +} + +weak_alias(__wcsxfrm_l, wcsxfrm_l); diff --git a/system/lib/libc/musl/src/locale/wcsxfrm_l.c b/system/lib/libc/musl/src/locale/wcsxfrm_l.c new file mode 100644 index 0000000000000..66a00193c7827 --- /dev/null +++ b/system/lib/libc/musl/src/locale/wcsxfrm_l.c @@ -0,0 +1,6 @@ +#include <wchar.h> + +size_t wcsxfrm_l(wchar_t *restrict dest, const wchar_t *restrict src, size_t n, locale_t locale) +{ + return wcsxfrm(dest, src, n); +} diff --git a/system/lib/libc/musl/src/locale/wctrans_l.c b/system/lib/libc/musl/src/locale/wctrans_l.c new file mode 100644 index 0000000000000..dae3381e11cf8 --- /dev/null +++ b/system/lib/libc/musl/src/locale/wctrans_l.c @@ -0,0 +1,6 @@ +#include <wctype.h> + +wctrans_t wctrans_l(const char *s, locale_t l) +{ + return wctrans(s); +} diff --git a/system/lib/libc/musl/src/locale/wctype_l.c b/system/lib/libc/musl/src/locale/wctype_l.c new file mode 100644 index 0000000000000..601bab37d163f --- /dev/null +++ b/system/lib/libc/musl/src/locale/wctype_l.c @@ -0,0 +1,9 @@ +#include <wctype.h> +#include "libc.h" + +wctype_t wctype_l(const char *s, locale_t l) +{ + return wctype(s); +} + +weak_alias(wctype_l, __wctype_l); diff --git a/system/lib/libc/musl/src/stdio/fwprintf.c b/system/lib/libc/musl/src/stdio/fwprintf.c new file mode 100644 index 0000000000000..9ce4f0102e1b5 --- /dev/null +++ b/system/lib/libc/musl/src/stdio/fwprintf.c @@ -0,0 +1,13 @@ +#include <stdio.h> +#include <stdarg.h> +#include <wchar.h> + +int fwprintf(FILE *restrict f, const wchar_t *restrict fmt, ...) +{ + int ret; + va_list ap; + va_start(ap, fmt); + ret = vfwprintf(f, fmt, ap); + va_end(ap); + return ret; +} diff --git a/system/lib/libc/musl/src/stdio/swprintf.c b/system/lib/libc/musl/src/stdio/swprintf.c new file mode 100644 index 0000000000000..cbf83d235d8e0 --- /dev/null +++ b/system/lib/libc/musl/src/stdio/swprintf.c @@ -0,0 +1,14 @@ +#include <stdio.h> +#include <stdarg.h> +#include <wchar.h> + +int swprintf(wchar_t *restrict s, size_t n, const wchar_t *restrict fmt, ...) +{ + int ret; + va_list ap; + va_start(ap, fmt); + ret = vswprintf(s, n, fmt, ap); + va_end(ap); + return ret; +} + diff --git a/system/lib/libc/musl/src/stdio/vfwprintf.c b/system/lib/libc/musl/src/stdio/vfwprintf.c new file mode 100644 index 0000000000000..eb0793121ed1b --- /dev/null +++ b/system/lib/libc/musl/src/stdio/vfwprintf.c @@ -0,0 +1,361 @@ +#include "stdio_impl.h" +#include <errno.h> +#include <ctype.h> +#include <limits.h> +#include <string.h> +#include <stdarg.h> +#include <wchar.h> +#include <inttypes.h> + +/* Convenient bit representation for modifier flags, which all fall + * within 31 codepoints of the space character. */ + +#define ALT_FORM (1U<<'#'-' ') +#define ZERO_PAD (1U<<'0'-' ') +#define LEFT_ADJ (1U<<'-'-' ') +#define PAD_POS (1U<<' '-' ') +#define MARK_POS (1U<<'+'-' ') +#define GROUPED (1U<<'\''-' ') + +#define FLAGMASK (ALT_FORM|ZERO_PAD|LEFT_ADJ|PAD_POS|MARK_POS|GROUPED) + +#if UINT_MAX == ULONG_MAX +#define LONG_IS_INT +#endif + +#if SIZE_MAX != ULONG_MAX || UINTMAX_MAX != ULLONG_MAX +#define ODD_TYPES +#endif + +/* State machine to accept length modifiers + conversion specifiers. + * Result is 0 on failure, or an argument type to pop on success. */ + +enum { + BARE, LPRE, LLPRE, HPRE, HHPRE, BIGLPRE, + ZTPRE, JPRE, + STOP, + PTR, INT, UINT, ULLONG, +#ifndef LONG_IS_INT + LONG, ULONG, +#else +#define LONG INT +#define ULONG UINT +#endif + SHORT, USHORT, CHAR, UCHAR, +#ifdef ODD_TYPES + LLONG, SIZET, IMAX, UMAX, PDIFF, UIPTR, +#else +#define LLONG ULLONG +#define SIZET ULONG +#define IMAX LLONG +#define UMAX ULLONG +#define PDIFF LONG +#define UIPTR ULONG +#endif + DBL, LDBL, + NOARG, + MAXSTATE +}; + +#define S(x) [(x)-'A'] + +static const unsigned char states[]['z'-'A'+1] = { + { /* 0: bare types */ + S('d') = INT, S('i') = INT, + S('o') = UINT, S('u') = UINT, S('x') = UINT, S('X') = UINT, + S('e') = DBL, S('f') = DBL, S('g') = DBL, S('a') = DBL, + S('E') = DBL, S('F') = DBL, S('G') = DBL, S('A') = DBL, + S('c') = CHAR, S('C') = INT, + S('s') = PTR, S('S') = PTR, S('p') = UIPTR, S('n') = PTR, + S('m') = NOARG, + S('l') = LPRE, S('h') = HPRE, S('L') = BIGLPRE, + S('z') = ZTPRE, S('j') = JPRE, S('t') = ZTPRE, + }, { /* 1: l-prefixed */ + S('d') = LONG, S('i') = LONG, + S('o') = ULONG, S('u') = ULONG, S('x') = ULONG, S('X') = ULONG, + S('c') = INT, S('s') = PTR, S('n') = PTR, + S('l') = LLPRE, + }, { /* 2: ll-prefixed */ + S('d') = LLONG, S('i') = LLONG, + S('o') = ULLONG, S('u') = ULLONG, + S('x') = ULLONG, S('X') = ULLONG, + S('n') = PTR, + }, { /* 3: h-prefixed */ + S('d') = SHORT, S('i') = SHORT, + S('o') = USHORT, S('u') = USHORT, + S('x') = USHORT, S('X') = USHORT, + S('n') = PTR, + S('h') = HHPRE, + }, { /* 4: hh-prefixed */ + S('d') = CHAR, S('i') = CHAR, + S('o') = UCHAR, S('u') = UCHAR, + S('x') = UCHAR, S('X') = UCHAR, + S('n') = PTR, + }, { /* 5: L-prefixed */ + S('e') = LDBL, S('f') = LDBL, S('g') = LDBL, S('a') = LDBL, + S('E') = LDBL, S('F') = LDBL, S('G') = LDBL, S('A') = LDBL, + S('n') = PTR, + }, { /* 6: z- or t-prefixed (assumed to be same size) */ + S('d') = PDIFF, S('i') = PDIFF, + S('o') = SIZET, S('u') = SIZET, + S('x') = SIZET, S('X') = SIZET, + S('n') = PTR, + }, { /* 7: j-prefixed */ + S('d') = IMAX, S('i') = IMAX, + S('o') = UMAX, S('u') = UMAX, + S('x') = UMAX, S('X') = UMAX, + S('n') = PTR, + } +}; + +#define OOB(x) ((unsigned)(x)-'A' > 'z'-'A') + +union arg +{ + uintmax_t i; + long double f; + void *p; +}; + +static void pop_arg(union arg *arg, int type, va_list *ap) +{ + /* Give the compiler a hint for optimizing the switch. */ + if ((unsigned)type > MAXSTATE) return; + switch (type) { + case PTR: arg->p = va_arg(*ap, void *); + break; case INT: arg->i = va_arg(*ap, int); + break; case UINT: arg->i = va_arg(*ap, unsigned int); +#ifndef LONG_IS_INT + break; case LONG: arg->i = va_arg(*ap, long); + break; case ULONG: arg->i = va_arg(*ap, unsigned long); +#endif + break; case ULLONG: arg->i = va_arg(*ap, unsigned long long); + break; case SHORT: arg->i = (short)va_arg(*ap, int); + break; case USHORT: arg->i = (unsigned short)va_arg(*ap, int); + break; case CHAR: arg->i = (signed char)va_arg(*ap, int); + break; case UCHAR: arg->i = (unsigned char)va_arg(*ap, int); +#ifdef ODD_TYPES + break; case LLONG: arg->i = va_arg(*ap, long long); + break; case SIZET: arg->i = va_arg(*ap, size_t); + break; case IMAX: arg->i = va_arg(*ap, intmax_t); + break; case UMAX: arg->i = va_arg(*ap, uintmax_t); + break; case PDIFF: arg->i = va_arg(*ap, ptrdiff_t); + break; case UIPTR: arg->i = (uintptr_t)va_arg(*ap, void *); +#endif + break; case DBL: arg->f = va_arg(*ap, double); + break; case LDBL: arg->f = va_arg(*ap, long double); + } +} + +static void out(FILE *f, const wchar_t *s, size_t l) +{ + while (l--) fputwc(*s++, f); +} + +static int getint(wchar_t **s) { + int i; + for (i=0; iswdigit(**s); (*s)++) + i = 10*i + (**s-'0'); + return i; +} + +static const char sizeprefix['y'-'a'] = { +['a'-'a']='L', ['e'-'a']='L', ['f'-'a']='L', ['g'-'a']='L', +['d'-'a']='j', ['i'-'a']='j', ['o'-'a']='j', ['u'-'a']='j', ['x'-'a']='j', +['p'-'a']='j' +}; + +static int wprintf_core(FILE *f, const wchar_t *fmt, va_list *ap, union arg *nl_arg, int *nl_type) +{ + wchar_t *a, *z, *s=(wchar_t *)fmt, *s0; + unsigned l10n=0, litpct, fl; + int w, p; + union arg arg; + int argpos; + unsigned st, ps; + int cnt=0, l=0; + int i; + int t; + char *bs; + char charfmt[16]; + wchar_t wc; + + for (;;) { + /* Update output count, end loop when fmt is exhausted */ + if (cnt >= 0) { + if (l > INT_MAX - cnt) { + if (!ferror(f)) errno = EOVERFLOW; + cnt = -1; + } else cnt += l; + } + if (!*s) break; + + /* Handle literal text and %% format specifiers */ + for (a=s; *s && *s!='%'; s++); + litpct = wcsspn(s, L"%")/2; /* Optimize %%%% runs */ + z = s+litpct; + s += 2*litpct; + l = z-a; + if (f) out(f, a, l); + if (l) continue; + + if (iswdigit(s[1]) && s[2]=='$') { + l10n=1; + argpos = s[1]-'0'; + s+=3; + } else { + argpos = -1; + s++; + } + + /* Read modifier flags */ + for (fl=0; (unsigned)*s-' '<32 && (FLAGMASK&(1U<<*s-' ')); s++) + fl |= 1U<<*s-' '; + + /* Read field width */ + if (*s=='*') { + if (iswdigit(s[1]) && s[2]=='$') { + l10n=1; + nl_type[s[1]-'0'] = INT; + w = nl_arg[s[1]-'0'].i; + s+=3; + } else if (!l10n) { + w = f ? va_arg(*ap, int) : 0; + s++; + } else return -1; + if (w<0) fl|=LEFT_ADJ, w=-w; + } else if ((w=getint(&s))<0) return -1; + + /* Read precision */ + if (*s=='.' && s[1]=='*') { + if (isdigit(s[2]) && s[3]=='$') { + nl_type[s[2]-'0'] = INT; + p = nl_arg[s[2]-'0'].i; + s+=4; + } else if (!l10n) { + p = f ? va_arg(*ap, int) : 0; + s+=2; + } else return -1; + } else if (*s=='.') { + s++; + p = getint(&s); + } else p = -1; + + /* Format specifier state machine */ + s0=s; + st=0; + do { + if (OOB(*s)) return -1; + ps=st; + st=states[st]S(*s++); + } while (st-1<STOP); + if (!st) return -1; + + /* Check validity of argument type (nl/normal) */ + if (st==NOARG) { + if (argpos>=0) return -1; + else if (!f) continue; + } else { + if (argpos>=0) nl_type[argpos]=st, arg=nl_arg[argpos]; + else if (f) pop_arg(&arg, st, ap); + else return 0; + } + + if (!f) continue; + t = s[-1]; + if (ps && (t&15)==3) t&=~32; + + switch (t) { + case 'n': + switch(ps) { + case BARE: *(int *)arg.p = cnt; break; + case LPRE: *(long *)arg.p = cnt; break; + case LLPRE: *(long long *)arg.p = cnt; break; + case HPRE: *(unsigned short *)arg.p = cnt; break; + case HHPRE: *(unsigned char *)arg.p = cnt; break; + case ZTPRE: *(size_t *)arg.p = cnt; break; + case JPRE: *(uintmax_t *)arg.p = cnt; break; + } + continue; + case 'c': + fputwc(btowc(arg.i), f); + l = 1; + continue; + case 'C': + fputwc(arg.i, f); + l = 1; + continue; + case 'S': + a = arg.p; + z = wmemchr(a, 0, p); + if (!z) z=a+p; + else p=z-a; + if (w<p) w=p; + if (!(fl&LEFT_ADJ)) fprintf(f, "%.*s", w-p, ""); + out(f, a, p); + if ((fl&LEFT_ADJ)) fprintf(f, "%.*s", w-p, ""); + l=w; + continue; + case 's': + bs = arg.p; + if (p<0) p = INT_MAX; + for (i=l=0; l<p && (i=mbtowc(&wc, bs, MB_LEN_MAX))>0; bs+=i, l++); + if (i<0) return -1; + p=l; + if (w<p) w=p; + if (!(fl&LEFT_ADJ)) fprintf(f, "%.*s", w-p, ""); + bs = arg.p; + while (l--) { + i=mbtowc(&wc, bs, MB_LEN_MAX); + bs+=i; + fputwc(wc, f); + } + if ((fl&LEFT_ADJ)) fprintf(f, "%.*s", w-p, ""); + l=w; + continue; + } + + snprintf(charfmt, sizeof charfmt, "%%%s%s%s%s%s*.*%c%c", + "#"+!(fl & ALT_FORM), + "+"+!(fl & MARK_POS), + "-"+!(fl & LEFT_ADJ), + " "+!(fl & PAD_POS), + "0"+!(fl & ZERO_PAD), + sizeprefix[(t|32)-'a'], t); + + switch (t|32) { + case 'a': case 'e': case 'f': case 'g': + l = fprintf(f, charfmt, w, p, arg.f); + break; + case 'd': case 'i': case 'o': case 'u': case 'x': case 'p': + l = fprintf(f, charfmt, w, p, arg.i); + break; + } + } + + if (f) return cnt; + if (!l10n) return 0; + + for (i=1; i<=NL_ARGMAX && nl_type[i]; i++) + pop_arg(nl_arg+i, nl_type[i], ap); + for (; i<=NL_ARGMAX && !nl_type[i]; i++); + if (i<=NL_ARGMAX) return -1; + return 1; +} + +int vfwprintf(FILE *restrict f, const wchar_t *restrict fmt, va_list ap) +{ + va_list ap2; + int nl_type[NL_ARGMAX] = {0}; + union arg nl_arg[NL_ARGMAX]; + int ret; + + va_copy(ap2, ap); + if (wprintf_core(0, fmt, &ap2, nl_arg, nl_type) < 0) return -1; + + FLOCK(f); + ret = wprintf_core(f, fmt, &ap2, nl_arg, nl_type); + FUNLOCK(f); + va_end(ap2); + return ret; +} diff --git a/system/lib/libc/musl/src/stdio/vswprintf.c b/system/lib/libc/musl/src/stdio/vswprintf.c new file mode 100644 index 0000000000000..7d237bae72e94 --- /dev/null +++ b/system/lib/libc/musl/src/stdio/vswprintf.c @@ -0,0 +1,53 @@ +#include "stdio_impl.h" +#include <limits.h> +#include <string.h> +#include <errno.h> +#include <stdint.h> +#include <wchar.h> + +struct cookie { + wchar_t *ws; + size_t l; +}; + +static size_t sw_write(FILE *f, const unsigned char *s, size_t l) +{ + size_t l0 = l; + int i = 0; + struct cookie *c = f->cookie; + if (s!=f->wbase && sw_write(f, f->wbase, f->wpos-f->wbase)==-1) + return -1; + while (c->l && l && (i=mbtowc(c->ws, (void *)s, l))>=0) { + s+=i; + l-=i; + c->l--; + c->ws++; + } + *c->ws = 0; + return i<0 ? i : l0; +} + +int vswprintf(wchar_t *restrict s, size_t n, const wchar_t *restrict fmt, va_list ap) +{ + int r; + FILE f; + unsigned char buf[256]; + struct cookie c = { s, n-1 }; + + memset(&f, 0, sizeof(FILE)); + f.lbf = EOF; + f.write = sw_write; + f.buf_size = sizeof buf; + f.buf = buf; + f.lock = -1; + f.cookie = &c; + if (!n) { + return -1; + } else if (n > INT_MAX) { + errno = EOVERFLOW; + return -1; + } + r = vfwprintf(&f, fmt, ap); + sw_write(&f, 0, 0); + return r>=n ? -1 : r; +} diff --git a/system/lib/libc/musl/src/stdio/vwprintf.c b/system/lib/libc/musl/src/stdio/vwprintf.c new file mode 100644 index 0000000000000..eeeecdc7c62ae --- /dev/null +++ b/system/lib/libc/musl/src/stdio/vwprintf.c @@ -0,0 +1,7 @@ +#include <stdio.h> +#include <wchar.h> + +int vwprintf(const wchar_t *restrict fmt, va_list ap) +{ + return vfwprintf(stdout, fmt, ap); +} diff --git a/system/lib/libc/musl/src/stdio/wprintf.c b/system/lib/libc/musl/src/stdio/wprintf.c new file mode 100644 index 0000000000000..342cd97911d36 --- /dev/null +++ b/system/lib/libc/musl/src/stdio/wprintf.c @@ -0,0 +1,13 @@ +#include <stdio.h> +#include <stdarg.h> +#include <wchar.h> + +int wprintf(const wchar_t *restrict fmt, ...) +{ + int ret; + va_list ap; + va_start(ap, fmt); + ret = vwprintf(fmt, ap); + va_end(ap); + return ret; +} diff --git a/system/lib/libcextra.symbols b/system/lib/libcextra.symbols index a365271d1f5a7..806b49dbe47ee 100644 --- a/system/lib/libcextra.symbols +++ b/system/lib/libcextra.symbols @@ -1,20 +1,41 @@ + W __iswctype_l + T __strxfrm_l + W __towlower_l + W __towupper_l + T __wcscoll_l + T __wcsxfrm_l + W __wctype_l T btowc T ecvt T fcvt + T fwprintf T gcvt T iswalnum + T iswalnum_l T iswalpha + T iswalpha_l T iswblank + T iswblank_l T iswcntrl + T iswcntrl_l T iswctype + T iswctype_l T iswdigit + T iswdigit_l T iswgraph + T iswgraph_l T iswlower + T iswlower_l T iswprint + T iswprint_l T iswpunct + T iswpunct_l T iswspace + T iswspace_l T iswupper + T iswupper_l T iswxdigit + T iswxdigit_l T mblen T mbrlen T mbrtowc @@ -23,21 +44,36 @@ T mbsrtowcs T mbstowcs T mbtowc + T strfmon + T strfmon_l + T strxfrm + W strxfrm_l + T swprintf T towctrans + T towctrans_l T towlower + T towlower_l T towupper + T towupper_l + T vfwprintf + T vswprintf + T vwprintf T wcpcpy T wcpncpy T wcrtomb T wcscasecmp + T wcscasecmp_l T wcscat T wcschr T wcscmp + T wcscoll + T wcscoll_l T wcscpy T wcscspn T wcsdup T wcslen T wcsncasecmp + T wcsncasecmp_l T wcsncat T wcsncmp T wcsncpy @@ -52,13 +88,18 @@ T wcstombs T wcswcs T wcswidth + T wcsxfrm + T wcsxfrm_l T wctob T wctomb T wctrans + T wctrans_l T wctype + T wctype_l T wcwidth T wmemchr T wmemcmp T wmemcpy T wmemmove T wmemset + T wprintf diff --git a/tests/cases/atomicrmw_unaligned.emcc b/tests/cases/atomicrmw_unaligned.emcc new file mode 100644 index 0000000000000..9faeda24c5b53 --- /dev/null +++ b/tests/cases/atomicrmw_unaligned.emcc @@ -0,0 +1 @@ +["-s", "UNALIGNED_MEMORY=1"] diff --git a/tests/cases/atomicrmw_unaligned.ll b/tests/cases/atomicrmw_unaligned.ll new file mode 100644 index 0000000000000..fe479dce038ed --- /dev/null +++ b/tests/cases/atomicrmw_unaligned.ll @@ -0,0 +1,21 @@ +; 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, %d,%d!\0A\00", align 1 ; [#uses=1 type=[15 x i8]*] + +; [#uses=0] +define i32 @main() { +entry: + %t = alloca i32, align 4 ; [#uses=2 type=i32**] + store i32 50, i32* %t, align 4 + %0 = load i32* %t + %1 = atomicrmw add i32* %t, i32 3 seq_cst, ; [#uses=0 type=i32] [debug line = 21:12] + %2 = load i32* %t + %call = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([15 x i8]* @.str, i32 0, i32 0), i32 %0, i32 %2) ; [#uses=0 type=i32] + %3 = atomicrmw volatile add i32* %t, i32 3 seq_cst, ; [#uses=0 type=i32] [debug line = 21:12] + ret i32 1 +} + +; [#uses=1] +declare i32 @printf(i8*, ...) diff --git a/tests/cases/atomicrmw_unaligned.txt b/tests/cases/atomicrmw_unaligned.txt new file mode 100644 index 0000000000000..45d16fb1f30e2 --- /dev/null +++ b/tests/cases/atomicrmw_unaligned.txt @@ -0,0 +1 @@ +hello, 50,53! diff --git a/tests/printf/output.txt b/tests/printf/output.txt index 19a6c1c2b2f24..0155f0da7b288 100644 --- a/tests/printf/output.txt +++ b/tests/printf/output.txt @@ -3,10 +3,15 @@ n=7 Characters: a A Decimals: 1977 650000 12 4 -Preceding with blanks: 1977 -Preceding with zeros: 0000001977 +Preceding with blanks: 1977 -1977 +Preceding with zeros: 0000001977 -000001977 +Force sign: +1977 -1977 +1977 -1977 +Force sign or space: 1977 -1977 1977 -1977 +Sign overrides space: +1977 -1977 +1977 -1977 Some different radixes: 100 64 144 0x64 0144 -floats: 3.14 +3e+00 3.141600E+00 +floats: 3.14 +3e+00 3.141600E+00 00003.14 +negative floats: -3.14 -3e+00 -3.141600E+00 -0003.14 +Force sign or space: 3.14 -3.14 3.14 -3.14 Width trick: 10 A string % Null string: (null) diff --git a/tests/printf/output_i64_1.txt b/tests/printf/output_i64_1.txt index 775f3f8d01f6a..e38fb78f1881a 100644 --- a/tests/printf/output_i64_1.txt +++ b/tests/printf/output_i64_1.txt @@ -3,10 +3,15 @@ n=7 Characters: a A Decimals: 1977 650000 12 4 -Preceding with blanks: 1977 -Preceding with zeros: 0000001977 +Preceding with blanks: 1977 -1977 +Preceding with zeros: 0000001977 -000001977 +Force sign: +1977 -1977 +1977 -1977 +Force sign or space: 1977 -1977 1977 -1977 +Sign overrides space: +1977 -1977 +1977 -1977 Some different radixes: 100 64 144 0x64 0144 -floats: 3.14 +3e+00 3.141600E+00 +floats: 3.14 +3e+00 3.141600E+00 00003.14 +negative floats: -3.14 -3e+00 -3.141600E+00 -0003.14 +Force sign or space: 3.14 -3.14 3.14 -3.14 Width trick: 10 A string % Null string: (null) diff --git a/tests/printf/test.c b/tests/printf/test.c index d05ba0965130b..1c8ad9f7691c2 100644 --- a/tests/printf/test.c +++ b/tests/printf/test.c @@ -8,10 +8,15 @@ int main() { printf("\n"); printf("Characters: %c %c\n", 'a', 65); printf("Decimals: %d %ld %lld %d\n", 1977, 650000L, 12LL, 4); - printf("Preceding with blanks: %10d\n", 1977); - printf("Preceding with zeros: %010d\n", 1977); + printf("Preceding with blanks: %10d %10d\n", 1977, -1977); + printf("Preceding with zeros: %010d %010d\n", 1977, -1977); + printf("Force sign: %+d %+d %+6d %+6d\n", 1977, -1977, 1977, -1977); + printf("Force sign or space: % d % d % 6d % 6d\n", 1977, -1977, 1977, -1977); + printf("Sign overrides space: % +d % +d % +6d % +6d\n", 1977, -1977, 1977, -1977); printf("Some different radixes: %d %x %o %#x %#o\n", 100, 100, 100, 100, 100); - printf("floats: %4.2f %+.0e %E\n", 3.1416, 3.1416, 3.1416); + printf("floats: %4.2f %+.0e %E %08.2f\n", 3.1416, 3.1416, 3.1416, 3.1416); + printf("negative floats: %4.2f %+.0e %E %08.2f\n", -3.1416, -3.1416, -3.1416, -3.1416); + printf("Force sign or space: % .2f % .2f % 6.2f % 6.2f\n", 3.1416, -3.1416, 3.1416, -3.1416); printf("Width trick: %*d\n", 5, 10); printf("%s %%\n", "A string"); printf("Null string: %7s\n", NULL); diff --git a/tests/sdl_audio_beep.cpp b/tests/sdl_audio_beep.cpp new file mode 100644 index 0000000000000..95a5a7e8c05f1 --- /dev/null +++ b/tests/sdl_audio_beep.cpp @@ -0,0 +1,246 @@ +#include <SDL/SDL.h> +#include <SDL/SDL_audio.h> +#include <queue> +#include <cmath> +#include <stdio.h> +#include <assert.h> + +#ifndef M_PI +#define M_PI 3.14159265358979323846f +#endif + +#ifdef EMSCRIPTEN +#include "emscripten/emscripten.h" +#endif + +#ifdef main +#undef main +#endif + +const int tone_duration = 1000; + +struct BeepObject { + double toneFrequency; + int samplesLeft; +}; + +class Beeper { +private: + double phase; + int frequency; + int numChannels; + int mutedChannel; +public: + Beeper(int frequency, int numChannels, int sdlAudioFormat); + ~Beeper(); + void beep(double toneFrequency, int durationMSecs); + template<typename T> + void generateSamples(T *stream, int length); + void wait(); + + std::queue<BeepObject> beeps; + int sdlAudioFormat; +}; + +void audio_callback(void*, Uint8*, int); + +Beeper::Beeper(int frequency_, int numChannels_, int sdlAudioFormat_) { + phase = 0.0; + mutedChannel = 1; + + SDL_AudioSpec desiredSpec; + + desiredSpec.freq = frequency_; + desiredSpec.format = sdlAudioFormat_; + desiredSpec.channels = numChannels_; + desiredSpec.samples = 1024; // This is samples per channel. + desiredSpec.callback = audio_callback; + desiredSpec.userdata = this; + + SDL_AudioSpec obtainedSpec; + + // you might want to look for errors here + SDL_OpenAudio(&desiredSpec, &obtainedSpec); + + // In this test, we require *exactly* the identical SDL result that we provide, since we test + // all various configurations individually. + if (obtainedSpec.freq != desiredSpec.freq || obtainedSpec.format != desiredSpec.format + || obtainedSpec.channels != desiredSpec.channels || obtainedSpec.samples != desiredSpec.samples) { + SDL_CloseAudio(); + throw std::runtime_error("Failed to initialize desired SDL_OpenAudio!"); + } + + frequency = obtainedSpec.freq; + numChannels = obtainedSpec.channels; + sdlAudioFormat = obtainedSpec.format; + + // Immediately start producing audio. + SDL_PauseAudio(0); +} + +Beeper::~Beeper() { + SDL_CloseAudio(); +} + +template<typename T> +void Beeper::generateSamples(T *stream, int length) { + const int AMPLITUDE = (sizeof(T) == 2) ? 28000 : 120; + const int offset = (sdlAudioFormat == AUDIO_U8) ? 120 : 0; + + int i = 0; + length /= numChannels; + while (i < length) { + if (beeps.empty()) { + memset(stream + numChannels*i, 0, sizeof(T)*numChannels*(length-i)); + return; + } + BeepObject& bo = beeps.front(); + + // In Stereo tests, mute one of the channels to be able to distinguish that Stereo output works. + if (bo.samplesLeft > tone_duration * frequency / 2 / 1000) { + mutedChannel = 1; + } else { + mutedChannel = 0; + } + + int samplesToDo = std::min(i + bo.samplesLeft, length); + bo.samplesLeft -= samplesToDo - i; + + while (i < samplesToDo) { + for(int j = 0; j < numChannels; ++j) { + stream[numChannels*i+j] = (T)(offset + (int)(AMPLITUDE * std::sin(phase * 2 * M_PI / frequency))); + if (numChannels > 1 && j == mutedChannel) { + stream[numChannels*i+j] = 0; + } + } + phase += bo.toneFrequency; + i++; + } + + if (bo.samplesLeft == 0) { + beeps.pop(); + } + } +} + +void Beeper::beep(double toneFrequency, int durationMSecs) { + BeepObject bo; + bo.toneFrequency = toneFrequency; + bo.samplesLeft = durationMSecs * frequency / 1000; + + SDL_LockAudio(); + beeps.push(bo); + SDL_UnlockAudio(); +} + +Beeper *beep = 0; + +// Test all kinds of various possible formats. Not all are supported, but running this +// test will report you which work. +const int freqs[] = { 8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000 }; +const int channels[] = { 1, 2 }; +const int sdlAudioFormats[] = { AUDIO_U8, AUDIO_S16LSB /*, AUDIO_S8, AUDIO_U16LSB, AUDIO_U16MSB, AUDIO_S16MSB */ }; + +const char *SdlAudioFormatToString(int sdlAudioType) { + switch(sdlAudioType) { + case AUDIO_U8: return "AUDIO_U8"; + case AUDIO_S8: return "AUDIO_S8"; + case AUDIO_U16LSB: return "AUDIO_U16LSB"; + case AUDIO_U16MSB: return "AUDIO_U16MSB"; + case AUDIO_S16LSB: return "AUDIO_S16LSB"; + case AUDIO_S16MSB: return "AUDIO_S16MSB"; + default: return "(unknown)"; + } +} + +#define NUM_ELEMS(x) (sizeof(x)/sizeof((x)[0])) + +// Indices to the currently running test. +int f = -1; +int c = 0; +int s = 0; + +void nextTest(void *unused = 0) { + ++f; + if (f >= NUM_ELEMS(freqs)) { + f = 0; + ++c; + if (c >= NUM_ELEMS(channels)) { + c = 0; + ++s; + if (s >= NUM_ELEMS(sdlAudioFormats)) { + printf("All tests done. Quit.\n"); +#ifdef EMSCRIPTEN + emscripten_cancel_main_loop(); +#ifdef REPORT_RESULT + int result = 1; + REPORT_RESULT(); +#endif +#endif + return; + } + } + } + + double Hz = 440; + try { + beep = new Beeper(freqs[f], channels[c], sdlAudioFormats[s]); + } catch(...) { + printf("FAILED to play beep for %d msecs at %d Hz tone with audio format %s, %d channels, and %d samples/sec.\n", + tone_duration, (int)Hz, SdlAudioFormatToString(sdlAudioFormats[s]), channels[c], freqs[f]); + nextTest(); + return; + } + + printf("Playing back a beep for %d msecs at %d Hz tone with audio format %s, %d channels, and %d samples/sec.\n", + tone_duration, (int)Hz, SdlAudioFormatToString(sdlAudioFormats[s]), channels[c], freqs[f]); + beep->beep(Hz, tone_duration); +} + +void update() { + SDL_LockAudio(); + int size = beep->beeps.size(); + SDL_UnlockAudio(); + if (size == 0 && beep) { + delete beep; + beep = 0; +#ifdef EMSCRIPTEN + emscripten_async_call(nextTest, 0, 1500); +#else + SDL_Delay(1500); + nextTest(); +#endif + } +} + +void audio_callback(void *_beeper, Uint8 *_stream, int _length) { + Beeper* beeper = (Beeper*) _beeper; + + if (beeper->sdlAudioFormat == AUDIO_U8) { + Uint8 *stream = (Uint8*) _stream; + beeper->generateSamples(stream, _length); + } else if (beeper->sdlAudioFormat == AUDIO_S16LSB) { + Sint16 *stream = (Sint16*) _stream; + int length = _length / 2; + beeper->generateSamples(stream, length); + } else { + assert(false && "Audio sample generation not implemented for current format!\n"); + } +} + +int main(int argc, char** argv) { + SDL_Init(SDL_INIT_AUDIO); + + nextTest(); + +#ifdef EMSCRIPTEN + emscripten_set_main_loop(update, 60, 0); +#else + while(beep) { + SDL_Delay(20); + update(); + } +#endif + + return 0; +} diff --git a/tests/sdl_canvas_alpha.c b/tests/sdl_canvas_alpha.c new file mode 100644 index 0000000000000..1a41d115b090f --- /dev/null +++ b/tests/sdl_canvas_alpha.c @@ -0,0 +1,46 @@ +#include <stdio.h> +#include <stdlib.h> +#include <SDL/SDL.h> +#include <SDL/SDL_ttf.h> +#include <emscripten.h> + + +int main(int argc, char **argv) { + SDL_Init(SDL_INIT_VIDEO); + SDL_Surface *screen = SDL_SetVideoMode(600, 450, 32, SDL_HWSURFACE); + + printf("Init: %d\n", TTF_Init()); + + TTF_Font *font = TTF_OpenFont("sans-serif", 40); + printf("Font: %p\n", font); + + SDL_Color color = { 0xff, 0x99, 0x00, 0xff }; + SDL_Surface *text = TTF_RenderText_Solid(font, "hello orange world", color); + + // render + for (int i = 0; i < 255; i++) { + SDL_Rect dest = { i, i, 0, 0 }; + SDL_SetAlpha(text, 0, (((float)i)/255)*(((float)i)/255)*255); + SDL_BlitSurface (text, NULL, screen, &dest); + } + + SDL_Flip(screen); + + SDL_LockSurface(screen); + + int width, height, isFullscreen; + emscripten_get_canvas_size(&width, &height, &isFullscreen); + + if (width != 600 && height != 450) + { + printf("error: wrong width/height\n"); + abort(); + } + + SDL_Quit(); + + printf("done.\n"); + + return 0; +} + diff --git a/tests/sdl_canvas_alpha.png b/tests/sdl_canvas_alpha.png new file mode 100644 index 0000000000000..fb9d61651fa5f Binary files /dev/null and b/tests/sdl_canvas_alpha.png differ diff --git a/tests/test_browser.py b/tests/test_browser.py index a3b9a1c31910d..8d000cd7a331c 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -19,6 +19,7 @@ def audio(): 'test_sdl_audio_mix_channels', 'test_sdl_audio_mix', 'test_sdl_audio_quickload', + 'test_sdl_audio_beeps', 'test_openal_playback', 'test_openal_buffers', 'test_freealut' @@ -649,6 +650,9 @@ def post(): self.btest('sdl_canvas_proxy.c', reference='sdl_canvas_proxy.png', args=['--proxy-to-worker', '--preload-file', 'data.txt'], manual_reference=True, post_build=post) + def test_sdl_canvas_alpha(self): + self.btest('sdl_canvas_alpha.c', reference='sdl_canvas_alpha.png') + def test_sdl_key(self): open(os.path.join(self.get_dir(), 'pre.js'), 'w').write(''' Module.postRun = function() { @@ -923,6 +927,13 @@ def test_sdl_audio_quickload(self): Popen([PYTHON, EMCC, '-O2', '--minify', '0', os.path.join(self.get_dir(), 'sdl_audio_quickload.c'), '-o', 'page.html', '-s', 'EXPORTED_FUNCTIONS=["_main", "_play"]']).communicate() self.run_browser('page.html', '', '/report_result?1') + def test_sdl_audio_beeps(self): + open(os.path.join(self.get_dir(), 'sdl_audio_beep.cpp'), 'w').write(self.with_report_result(open(path_from_root('tests', 'sdl_audio_beep.cpp')).read())) + + # use closure to check for a possible bug with closure minifying away newer Audio() attributes + Popen([PYTHON, EMCC, '-O2', '--closure', '1', '--minify', '0', os.path.join(self.get_dir(), 'sdl_audio_beep.cpp'), '-s', 'DISABLE_EXCEPTION_CATCHING=0', '-o', 'page.html']).communicate() + self.run_browser('page.html', '', '/report_result?1') + def test_sdl_gl_read(self): # SDL, OpenGL, readPixels open(os.path.join(self.get_dir(), 'sdl_gl_read.c'), 'w').write(self.with_report_result(open(path_from_root('tests', 'sdl_gl_read.c')).read())) diff --git a/tests/test_core.py b/tests/test_core.py index 4c17a942ee090..d1d3bab08e64e 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1372,6 +1372,50 @@ def test_floatvars(self): ''' self.do_run(src, '*1,10,10.5,1,1.2340,0.00*\n0.50, 3.30, 3.30, 3.30\nsmall: 0.0000010000\n') + def test_zerodiv(self): + self.do_run(r''' + #include <stdio.h> + int main(int argc, const char* argv[]) + { + float f1 = 1.0f; + float f2 = 0.0f; + float f_zero = 0.0f; + + float f3 = 0.0f / f2; + float f4 = f2 / 0.0f; + float f5 = f2 / f2; + float f6 = f2 / f_zero; + + printf("f3: %f\n", f3); + printf("f4: %f\n", f4); + printf("f5: %f\n", f5); + printf("f6: %f\n", f6); + + return 0; + } + ''', '''f3: nan +f4: nan +f5: nan +f6: nan +''') + + def test_zero_multiplication(self): + src = ''' + #include <stdio.h> + int main(int argc, char * argv[]) { + int one = argc; + + printf("%d ", 0 * one); + printf("%d ", 0 * -one); + printf("%d ", -one * 0); + printf("%g ", 0.0 * one); + printf("%g ", 0.0 * -one); + printf("%g", -one * 0.0); + return 0; + } + ''' + self.do_run(src, '0 0 0 0 -0 -0') + def test_isnan(self): src = r''' #include <stdio.h> @@ -8937,6 +8981,8 @@ def test_lifetime(self): def test_cases(self): if Building.LLVM_OPTS: return self.skip("Our code is not exactly 'normal' llvm assembly") + emcc_args = self.emcc_args + try: os.environ['EMCC_LEAVE_INPUTS_RAW'] = '1' Settings.CHECK_OVERFLOWS = 0 @@ -8950,6 +8996,10 @@ def test_cases(self): if '_noasm' in shortname and Settings.ASM_JS: print self.skip('case "%s" not relevant for asm.js' % shortname) continue + self.emcc_args = emcc_args + if os.path.exists(shortname + '.emcc'): + if not self.emcc_args: continue + self.emcc_args = self.emcc_args + json.loads(open(shortname + '.emcc').read()) print >> sys.stderr, "Testing case '%s'..." % shortname output_file = path_from_root('tests', 'cases', shortname + '.txt') if Settings.QUANTUM_SIZE == 1: @@ -8970,6 +9020,7 @@ def test_cases(self): finally: del os.environ['EMCC_LEAVE_INPUTS_RAW'] + self.emcc_args = emcc_args def test_fuzz(self): if Settings.USE_TYPED_ARRAYS != 2: return self.skip('needs ta2') diff --git a/tests/test_other.py b/tests/test_other.py index 56c13650c5750..9f331439911e2 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -1665,10 +1665,10 @@ def test_chunking(self): if multiprocessing.cpu_count() < 2: return self.skip('need multiple cores') try: os.environ['EMCC_DEBUG'] = '1' - os.environ['EMCC_CORES'] = '2' + os.environ['EMCC_CORES'] = '2' # standardize over machines for asm, linkable, chunks, js_chunks in [ - (0, 0, 3, 2), (0, 1, 3, 4), - (1, 0, 3, 2), (1, 1, 3, 4) + (0, 0, 2, 2), (0, 1, 2, 4), + (1, 0, 2, 2), (1, 1, 2, 4) ]: print asm, linkable, chunks, js_chunks output, err = Popen([PYTHON, EMCC, path_from_root('tests', 'hello_libcxx.cpp'), '-O1', '-s', 'LINKABLE=%d' % linkable, '-s', 'ASM_JS=%d' % asm] + (['-O2'] if asm else []), stdout=PIPE, stderr=PIPE).communicate() diff --git a/tools/js_optimizer.py b/tools/js_optimizer.py index 12e76223e6110..d6f8921c9bb21 100644 --- a/tools/js_optimizer.py +++ b/tools/js_optimizer.py @@ -377,5 +377,7 @@ def run(filename, passes, js_engine=shared.NODE_JS, jcache=False, source_map=Fal sys.argv = sys.argv[:-1] else: extra_info = None - run(sys.argv[1], sys.argv[2:], extra_info=extra_info) + out = run(sys.argv[1], sys.argv[2:], extra_info=extra_info) + import shutil + shutil.copyfile(out, sys.argv[1] + '.jsopt.js') diff --git a/tools/shared.py b/tools/shared.py index 4e1a3ebf163fe..4d0f92b5508b9 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -304,7 +304,7 @@ def find_temp_directory(): # we re-check sanity when the settings are changed) # We also re-check sanity and clear the cache when the version changes -EMSCRIPTEN_VERSION = '1.6.1' +EMSCRIPTEN_VERSION = '1.6.2' def generate_sanity(): return EMSCRIPTEN_VERSION + '|' + get_llvm_target() + '|' + LLVM_ROOT @@ -1413,6 +1413,9 @@ def ensure_relooper(relooper): emcc_debug = os.environ.get('EMCC_DEBUG') if emcc_debug: del os.environ['EMCC_DEBUG'] + emcc_leave_inputs_raw = os.environ.get('EMCC_LEAVE_INPUTS_RAW') + if emcc_leave_inputs_raw: del os.environ['EMCC_LEAVE_INPUTS_RAW'] + def make(opt_level): raw = relooper + '.raw.js' Building.emcc(os.path.join('relooper', 'Relooper.cpp'), ['-I' + os.path.join('relooper'), '--post-js', @@ -1442,8 +1445,10 @@ def make(opt_level): finally: os.chdir(curr) if emcc_debug: os.environ['EMCC_DEBUG'] = emcc_debug + if emcc_leave_inputs_raw: os.environ['EMCC_LEAVE_INPUTS_RAW'] = emcc_leave_inputs_raw if not ok: logging.error('bootstrapping relooper failed. You may need to manually create relooper.js by compiling it, see src/relooper/emscripten') + try_delete(relooper) # do not leave a phase-1 version if phase 2 broke 1/0 @staticmethod