diff --git a/AUTHORS b/AUTHORS index 710ab203505c1..04dfc10da4012 100644 --- a/AUTHORS +++ b/AUTHORS @@ -43,4 +43,5 @@ a license to everyone to use it as detailed in LICENSE.) * Xuejie Xiao * Dominic Wong * Alan Kligman (copyright owned by Mozilla Foundation) +* Anthony Liot diff --git a/emcc b/emcc index 7c971b71dd871..f65c040538772 100755 --- a/emcc +++ b/emcc @@ -95,7 +95,7 @@ TEMP_DIR = os.environ.get('EMCC_TEMP_DIR') LEAVE_INPUTS_RAW = os.environ.get('EMCC_LEAVE_INPUTS_RAW') # Do not compile .ll files into .bc, just compile them with emscripten directly # Not recommended, this is mainly for the test runner, or if you have some other # specific need. - # One major limitation with this mode is that dlmalloc and libc++ cannot be + # One major limitation with this mode is that libc and libc++ cannot be # added in. Also, LLVM optimizations will not be done, nor dead code elimination AUTODEBUG = os.environ.get('EMCC_AUTODEBUG') # If set to 1, we will run the autodebugger (the automatic debugging tool, see tools/autodebugger). # Note that this will disable inclusion of libraries. This is useful because including @@ -338,7 +338,7 @@ Options that are modified or new in %s include: --clear-cache Manually clears the cache of compiled emscripten system libraries (libc++, - libc++abi, dlmalloc). This is normally + libc++abi, libc). This is normally handled automatically, but if you update llvm in-place (instead of having a different directory for a new version), the caching @@ -353,9 +353,9 @@ Options that are modified or new in %s include: The target file, if specified (-o ), defines what will be generated: - .js JavaScript (default) + .js JavaScript .html HTML with embedded JavaScript - .bc LLVM bitcode + .bc LLVM bitcode (default) .o LLVM bitcode (same as .bc) The -c option (which tells gcc not to run the linker) will @@ -934,16 +934,16 @@ try: # Note that we assume a single symbol is enough to know if we have/do not have dlmalloc etc. If you # include just a few symbols but want the rest, this will not work. - # dlmalloc - def create_dlmalloc(): - if DEBUG: print >> sys.stderr, 'emcc: building dlmalloc for cache' + # libc + def create_libc(): + if DEBUG: print >> sys.stderr, 'emcc: building libc for cache' execute([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'dlmalloc.c'), '-g', '-o', in_temp('dlmalloc.o')], stdout=stdout, stderr=stderr) # we include the libc++ new stuff here, so that the common case of using just new/delete is quick to link execute([shared.PYTHON, shared.EMXX, shared.path_from_root('system', 'lib', 'libcxx', 'new.cpp'), '-g', '-o', in_temp('new.o')], stdout=stdout, stderr=stderr) - shared.Building.link([in_temp('dlmalloc.o'), in_temp('new.o')], in_temp('dlmalloc_full.o')) - return in_temp('dlmalloc_full.o') - def fix_dlmalloc(): - # dlmalloc needs some sign correction. # If we are in mode 0, switch to 2. We will add our lines + shared.Building.link([in_temp('dlmalloc.o'), in_temp('new.o')], in_temp('libc.o')) + return in_temp('libc.o') + def fix_libc(): + # libc needs some sign correction. # If we are in mode 0, switch to 2. We will add our lines try: if shared.Settings.CORRECT_SIGNS == 0: raise Exception('we need to change to 2') except: # we fail if equal to 0 - so we need to switch to 2 - or if CORRECT_SIGNS is not even in Settings @@ -954,7 +954,7 @@ try: # so all is well anyhow too. # XXX We also need to add libc symbols that use malloc, for example strdup. It's very rare to use just them and not # a normal malloc symbol (like free, after calling strdup), so we haven't hit this yet, but it is possible. - dlmalloc_symbols = open(shared.path_from_root('system', 'lib', 'dlmalloc.symbols')).read().split('\n') + libc_symbols = open(shared.path_from_root('system', 'lib', 'libc.symbols')).read().split('\n') # libcxx def create_libcxx(): @@ -972,7 +972,7 @@ try: shared.Settings.CORRECT_SIGNS = shared.Settings.CORRECT_OVERFLOWS = shared.Settings.CORRECT_ROUNDINGS = 1 #print >> sys.stderr, 'emcc: info: using libcxx turns on CORRECT_* options' libcxx_symbols = map(lambda line: line.strip().split(' ')[1], open(shared.path_from_root('system', 'lib', 'libcxx', 'symbols')).readlines()) - libcxx_symbols = filter(lambda symbol: symbol not in dlmalloc_symbols, libcxx_symbols) + libcxx_symbols = filter(lambda symbol: symbol not in libc_symbols, libcxx_symbols) libcxx_symbols = set(libcxx_symbols) # libcxxabi - just for dynamic_cast for now @@ -990,14 +990,14 @@ try: #print >> sys.stderr, 'emcc: info: using libcxxabi, this may need CORRECT_* options' #shared.Settings.CORRECT_SIGNS = shared.Settings.CORRECT_OVERFLOWS = shared.Settings.CORRECT_ROUNDINGS = 1 libcxxabi_symbols = map(lambda line: line.strip().split(' ')[1], open(shared.path_from_root('system', 'lib', 'libcxxabi', 'symbols')).readlines()) - libcxxabi_symbols = filter(lambda symbol: symbol not in dlmalloc_symbols, libcxxabi_symbols) + libcxxabi_symbols = filter(lambda symbol: symbol not in libc_symbols, libcxxabi_symbols) libcxxabi_symbols = set(libcxxabi_symbols) - force = False # If we have libcxx, we must force inclusion of dlmalloc, since libcxx uses new internally. Note: this is kind of hacky + force = False # If we have libcxx, we must force inclusion of libc, since libcxx uses new internally. Note: this is kind of hacky for name, create, fix, library_symbols in [('libcxx', create_libcxx, fix_libcxx, libcxx_symbols), ('libcxxabi', create_libcxxabi, fix_libcxxabi, libcxxabi_symbols), - ('dlmalloc', create_dlmalloc, fix_dlmalloc, dlmalloc_symbols)]: + ('libc', create_libc, fix_libc, libc_symbols)]: need = set() has = set() for temp_file in temp_files: @@ -1186,9 +1186,14 @@ try: else: return 'eliminate' - js_optimizer_queue += [get_eliminate()] + def get_simplify_pre(): + if shared.Settings.ASM_JS: + return 'simplifyExpressionsPreAsm' + else: + return 'simplifyExpressionsPre' + + js_optimizer_queue += [get_eliminate(), get_simplify_pre()] - js_optimizer_queue += ['simplifyExpressionsPre'] if shared.Settings.RELOOP: js_optimizer_queue += ['optimizeShiftsAggressive', get_eliminate()] # aggressive shifts optimization requires loops, it breaks on switches diff --git a/emscripten.py b/emscripten.py index 204cbbec5af1e..6b990bd37370c 100755 --- a/emscripten.py +++ b/emscripten.py @@ -377,6 +377,7 @@ def make_table(sig, raw): ''' % (asm_setup,) + '\n' + asm_global_vars + ''' var __THREW__ = 0; var undef = 0; + var tempInt = 0, tempValue = 0; ''' + ''.join([''' var tempRet%d = 0;''' % i for i in range(10)]) + '\n' + asm_global_funcs + ''' function stackAlloc(size) { diff --git a/src/jsifier.js b/src/jsifier.js index 24af5b6ddc197..4b422bca0d546 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -1302,6 +1302,7 @@ function JSify(data, functionsOnly, givenFunctions) { }).filter(function(arg) { return arg !== null; }).join(',') + ',tempInt)'; + varargs = asmCoercion(varargs, 'i32'); } args = args.concat(varargs); @@ -1329,6 +1330,9 @@ function JSify(data, functionsOnly, givenFunctions) { var ret = ident + '(' + args.join(', ') + ')'; if (ASM_JS) { // TODO: do only when needed (library functions and Math.*?) XXX && shortident in Functions.libraryFunctions) { ret = asmCoercion(ret, returnType); + if (shortident == 'abort' && funcData.returnType != 'void') { + ret += '; return 0'; // special case: abort() can happen without return, breaking the return type of asm functions. ensure a return + } } return ret; } diff --git a/src/library.js b/src/library.js index 1e528d0668494..82e42a28e2d66 100644 --- a/src/library.js +++ b/src/library.js @@ -5104,6 +5104,8 @@ LibraryManager.library = { } }, + _ZNSt9exceptionD2Ev: function(){}, // XXX a dependency of dlmalloc, but not actually needed if libcxx is not anyhow included + // RTTI hacks for exception handling, defining type_infos for common types. // The values are dummies. We simply use the addresses of these statically // allocated variables as unique identifiers. @@ -6779,8 +6781,12 @@ LibraryManager.library = { $Sockets__deps: ['__setErrNo', '$ERRNO_CODES'], $Sockets: { + BACKEND_WEBSOCKETS: 0, + BACKEND_WEBRTC: 1, BUFFER_SIZE: 10*1024, // initial size MAX_BUFFER_SIZE: 10*1024*1024, // maximum size we will grow the buffer + + backend: 0, // default to websockets nextFd: 1, fds: {}, sockaddr_in_layout: Runtime.generateStructInfo([ @@ -6798,6 +6804,111 @@ LibraryManager.library = { ['i32', 'msg_controllen'], ['i32', 'msg_flags'], ]), + + backends: { + 0: { // websockets + connect: function(info) { + console.log('opening ws://' + info.host + ':' + info.port); + info.socket = new WebSocket('ws://' + info.host + ':' + info.port, ['binary']); + info.socket.binaryType = 'arraybuffer'; + + var i32Temp = new Uint32Array(1); + var i8Temp = new Uint8Array(i32Temp.buffer); + + info.inQueue = []; + if (!info.stream) { + var partialBuffer = null; // inQueue contains full dgram messages; this buffers incomplete data. Must begin with the beginning of a message + } + + info.socket.onmessage = function(event) { + assert(typeof event.data !== 'string' && event.data.byteLength); // must get binary data! + var data = new Uint8Array(event.data); // make a typed array view on the array buffer +#if SOCKET_DEBUG + Module.print(['onmessage', data.length, '|', Array.prototype.slice.call(data)]); +#endif + if (info.stream) { + info.inQueue.push(data); + } else { + // we added headers with message sizes, read those to find discrete messages + if (partialBuffer) { + // append to the partial buffer + var newBuffer = new Uint8Array(partialBuffer.length + data.length); + newBuffer.set(partialBuffer); + newBuffer.set(data, partialBuffer.length); + // forget the partial buffer and work on data + data = newBuffer; + partialBuffer = null; + } + var currPos = 0; + while (currPos+4 < data.length) { + i8Temp.set(data.subarray(currPos, currPos+4)); + var currLen = i32Temp[0]; + assert(currLen > 0); + if (currPos + 4 + currLen > data.length) { + break; // not enough data has arrived + } + currPos += 4; +#if SOCKET_DEBUG + Module.print(['onmessage message', currLen, '|', Array.prototype.slice.call(data.subarray(currPos, currPos+currLen))]); +#endif + info.inQueue.push(data.subarray(currPos, currPos+currLen)); + currPos += currLen; + } + // If data remains, buffer it + if (currPos < data.length) { + partialBuffer = data.subarray(currPos); + } + } + } + function send(data) { + // TODO: if browser accepts views, can optimize this +#if SOCKET_DEBUG + Module.print('sender actually sending ' + Array.prototype.slice.call(data)); +#endif + // ok to use the underlying buffer, we created data and know that the buffer starts at the beginning + info.socket.send(data.buffer); + } + var outQueue = []; + var intervalling = false, interval; + function trySend() { + if (info.socket.readyState != info.socket.OPEN) { + if (!intervalling) { + intervalling = true; + console.log('waiting for socket in order to send'); + interval = setInterval(trySend, 100); + } + return; + } + for (var i = 0; i < outQueue.length; i++) { + send(outQueue[i]); + } + outQueue.length = 0; + if (intervalling) { + intervalling = false; + clearInterval(interval); + } + } + info.sender = function(data) { + if (!info.stream) { + // add a header with the message size + var header = new Uint8Array(4); + i32Temp[0] = data.length; + header.set(i8Temp); + outQueue.push(header); + } + outQueue.push(new Uint8Array(data)); + trySend(); + }; + } + }, + 1: { // webrtc + } + } + }, + + emscripten_set_network_backend__deps: ['$Sockets'], + emscripten_set_network_backend: function(backend) { + Sockets.backend = backend; }, socket__deps: ['$Sockets'], @@ -6808,6 +6919,9 @@ LibraryManager.library = { if (protocol) { assert(stream == (protocol == {{{ cDefine('IPPROTO_TCP') }}})); // if stream, must be tcp } + if (Sockets.backend == Sockets.BACKEND_WEBRTC) { + assert(!stream); // If WebRTC, we can only support datagram, not stream + } Sockets.fds[fd] = { connected: false, stream: stream @@ -6831,98 +6945,7 @@ LibraryManager.library = { info.host = _gethostbyname.table[low + 0xff*high]; assert(info.host, 'problem translating fake ip ' + parts); } - console.log('opening ws://' + info.host + ':' + info.port); - info.socket = new WebSocket('ws://' + info.host + ':' + info.port, ['binary']); - info.socket.binaryType = 'arraybuffer'; - - var i32Temp = new Uint32Array(1); - var i8Temp = new Uint8Array(i32Temp.buffer); - - info.inQueue = []; - if (!info.stream) { - var partialBuffer = null; // inQueue contains full dgram messages; this buffers incomplete data. Must begin with the beginning of a message - } - - info.socket.onmessage = function(event) { - assert(typeof event.data !== 'string' && event.data.byteLength); // must get binary data! - var data = new Uint8Array(event.data); // make a typed array view on the array buffer -#if SOCKET_DEBUG - Module.print(['onmessage', data.length, '|', Array.prototype.slice.call(data)]); -#endif - if (info.stream) { - info.inQueue.push(data); - } else { - // we added headers with message sizes, read those to find discrete messages - if (partialBuffer) { - // append to the partial buffer - var newBuffer = new Uint8Array(partialBuffer.length + data.length); - newBuffer.set(partialBuffer); - newBuffer.set(data, partialBuffer.length); - // forget the partial buffer and work on data - data = newBuffer; - partialBuffer = null; - } - var currPos = 0; - while (currPos+4 < data.length) { - i8Temp.set(data.subarray(currPos, currPos+4)); - var currLen = i32Temp[0]; - assert(currLen > 0); - if (currPos + 4 + currLen > data.length) { - break; // not enough data has arrived - } - currPos += 4; -#if SOCKET_DEBUG - Module.print(['onmessage message', currLen, '|', Array.prototype.slice.call(data.subarray(currPos, currPos+currLen))]); -#endif - info.inQueue.push(data.subarray(currPos, currPos+currLen)); - currPos += currLen; - } - // If data remains, buffer it - if (currPos < data.length) { - partialBuffer = data.subarray(currPos); - } - } -#endif - } - function send(data) { - // TODO: if browser accepts views, can optimize this -#if SOCKET_DEBUG - Module.print('sender actually sending ' + Array.prototype.slice.call(data)); -#endif - // ok to use the underlying buffer, we created data and know that the buffer starts at the beginning - info.socket.send(data.buffer); - } - var outQueue = []; - var intervalling = false, interval; - function trySend() { - if (info.socket.readyState != info.socket.OPEN) { - if (!intervalling) { - intervalling = true; - console.log('waiting for socket in order to send'); - interval = setInterval(trySend, 100); - } - return; - } - for (var i = 0; i < outQueue.length; i++) { - send(outQueue[i]); - } - outQueue.length = 0; - if (intervalling) { - intervalling = false; - clearInterval(interval); - } - } - info.sender = function(data) { - if (!info.stream) { - // add a header with the message size - var header = new Uint8Array(4); - i32Temp[0] = data.length; - header.set(i8Temp); - outQueue.push(header); - } - outQueue.push(new Uint8Array(data)); - trySend(); - }; + Sockets.backends[Sockets.backend].connect(info); return 0; }, diff --git a/src/library_browser.js b/src/library_browser.js index 32a7d2ccd19d0..fd09f47812205 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -396,6 +396,55 @@ mergeInto(LibraryManager.library, { }, true /* no need for run dependency, this is async but will not do any prepare etc. step */ ); }, + emscripten_async_wget2: function(url, file, request, param, arg, onload, onerror, onprogress) { + var _url = Pointer_stringify(url); + var _file = Pointer_stringify(file); + var _request = Pointer_stringify(request); + var _param = Pointer_stringify(param); + var index = _file.lastIndexOf('/'); + + var http = new XMLHttpRequest(); + http.open(_request, _url, true); + http.responseType = 'arraybuffer'; + + // LOAD + http.onload = function(e) { + if (http.status == 200) { + FS.createDataFile( _file.substr(0, index), _file.substr(index + 1), new Uint8Array(http.response), true, true); + if (onload) FUNCTION_TABLE[onload](arg, file); + } else { + if (onerror) FUNCTION_TABLE[onerror](arg, http.status); + } + }; + + // ERROR + http.onerror = function(e) { + if (onerror) FUNCTION_TABLE[onerror](arg, http.status); + }; + + // PROGRESS + http.onprogress = function(e) { + var percentComplete = (e.position / e.totalSize)*100; + if (onprogress) FUNCTION_TABLE[onprogress](arg, percentComplete); + }; + + // Useful because the browser can limit the number of redirection + try { + if (http.channel instanceof Ci.nsIHttpChannel) + http.channel.redirectionLimit = 0; + } catch (ex) { /* whatever */ } + + if (_request == "POST") { + //Send the proper header information along with the request + http.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + http.setRequestHeader("Content-length", _param.length); + http.setRequestHeader("Connection", "close"); + http.send(_param); + } else { + http.send(null); + } + }, + emscripten_async_prepare: function(file, onload, onerror) { var _file = Pointer_stringify(file); var data = FS.analyzePath(_file); diff --git a/src/parseTools.js b/src/parseTools.js index 89267d102681e..c352621d4330d 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -999,7 +999,7 @@ function makeVarDef(js) { function asmEnsureFloat(value, type) { // ensures that a float type has either 5.5 (clearly a float) or +5 (float due to asm coercion) if (!ASM_JS) return value; - if (!isIntImplemented(type) && isNumber(value) && value.toString().indexOf('.') < 0) { + if (type in Runtime.FLOAT_TYPES && isNumber(value) && value.toString().indexOf('.') < 0) { return '(+(' + value + '))'; } else { return value; @@ -1007,10 +1007,10 @@ function asmEnsureFloat(value, type) { // ensures that a float type has either 5 } function asmInitializer(type, impl) { - if (isIntImplemented(type)) {// || (impl && impl == 'VAR_EMULATED')) { - return '0'; - } else { + if (type in Runtime.FLOAT_TYPES) { return '+0'; + } else { + return '0'; } } @@ -1018,10 +1018,10 @@ function asmCoercion(value, type) { if (!ASM_JS) return value; if (type == 'void') { return value; - } else if (isIntImplemented(type)) { - return '((' + value + ')|0)'; - } else { + } else if (type in Runtime.FLOAT_TYPES) { return '(+(' + value + '))'; + } else { + return '((' + value + ')|0)'; } } diff --git a/system/include/emscripten/emscripten.h b/system/include/emscripten/emscripten.h index 70938fb013d83..93551f39fecc6 100644 --- a/system/include/emscripten/emscripten.h +++ b/system/include/emscripten/emscripten.h @@ -211,6 +211,23 @@ void emscripten_async_wget(const char* url, const char* file, void (*onload)(con */ void emscripten_async_wget_data(const char* url, void *arg, void (*onload)(void*, void*, int), void (*onerror)(void*)); +/* + * More feature-complete version of emscripten_async_wget. Note: + * this version is experimental. + * + * The requestype is 'GET' or 'POST', + * If is post request, param is the post parameter + * like key=value&key2=value2. + * The param 'arg' is a pointer will be pass to the callback + * When file is ready then 'onload' callback will called. + * During the download 'onprogress' callback will called. + * If any error occurred 'onerror' will called. + * The callbacks are called with an object pointer give in parameter + * and file if is a success, the progress value during progress + * and http status code if is an error. + */ +void emscripten_async_wget2(const char* url, const char* file, const char* requesttype, const char* param, void *arg, void (*onload)(void*, const char*), void (*onerror)(void*, int), void (*onprogress)(void*, int)); + /* * Prepare a file in asynchronous way. This does just the * preparation part of emscripten_async_wget, that is, it @@ -304,6 +321,17 @@ void emscripten_worker_respond(char *data, int size); */ int emscripten_get_worker_queue_size(worker_handle worker); +/* + * Select the networking backend to use. By default emscripten's + * socket/networking implementation will use websockets, with this + * function you can change that to WebRTC. + * This function must be called before any network functions are + * called. + */ +#define EMSCRIPTEN_NETWORK_WEBSOCKETS 0 +#define EMSCRIPTEN_NETWORK_WEBRTC 1 +void emscripten_set_network_backend(int backend); + /* * Profiling tools. * INIT must be called first, with the maximum identifier that diff --git a/system/lib/dlmalloc.symbols b/system/lib/libc.symbols similarity index 100% rename from system/lib/dlmalloc.symbols rename to system/lib/libc.symbols diff --git a/tests/enet_client.c b/tests/enet_client.c index 78c8f314d88be..601b8769e0824 100644 --- a/tests/enet_client.c +++ b/tests/enet_client.c @@ -10,7 +10,7 @@ void main_loop() { #if EMSCRIPTEN counter++; #endif - if (counter == 10) { + if (counter == 100) { printf("stop!\n"); emscripten_cancel_main_loop(); return; diff --git a/tests/enet_server.c b/tests/enet_server.c index 87c64038b10f4..a8167e162ca9f 100644 --- a/tests/enet_server.c +++ b/tests/enet_server.c @@ -29,7 +29,7 @@ void main_loop() { #if EMSCRIPTEN counter++; #endif - if (counter == 10) { + if (counter == 100) { printf("stop!\n"); emscripten_cancel_main_loop(); return; diff --git a/tests/http.cpp b/tests/http.cpp new file mode 100644 index 0000000000000..07931e3c9b3e8 --- /dev/null +++ b/tests/http.cpp @@ -0,0 +1,286 @@ +// +// http.cpp +// Player Javascript +// +// Created by Anthony Liot on 23/11/12. +// + +#include "http.h" +#include +#include +#include + +int http::uid = 0; + +/* +- Useful for download an url on other domain + +*/ +// http://..../download.php?url= +std::string http::cross_domain = ""; + + +//---------------------------------------------------------------------------------------- +// HTTP CLASS +//---------------------------------------------------------------------------------------- + +void http::onLoaded(void* parent, const char * file) { + http* req = reinterpret_cast(parent); + req->onLoaded(file); +} + +void http::onError(void* parent, int statuserror) { + http* req = reinterpret_cast(parent); + req->onError(statuserror); +} + +void http::onProgress(void* parent, int progress) { + http* req = reinterpret_cast(parent); + req->onProgress(progress); +} + +/** +* Constructeur +*/ +http::http(const char* hostname, int requestType, const char* targetFilename) : _hostname(hostname), _page(""), _targetFileName(targetFilename), _param(""), _content(""), _error(""), _request((RequestType)requestType), _status(ST_PENDING), _assync(ASSYNC_THREAD) { + _progressValue = -1; + _uid = uid++; +} + + +/** +* Destructeur +*/ +http::~http() { +} + +/** +* Effectue la requete +*/ +void http::runRequest(const char* page, int assync) { + _page = page; + _status = ST_PENDING; + _assync = (AssyncMode)assync; + _progressValue = 0; + + std::string url = cross_domain; + url += _hostname + _page; + + if (_hostname.size() > 0 && _page.size() > 0) { + + printf("URL : %s\n",url.c_str()); + printf("REQUEST : %s\n",(_request==REQUEST_GET) ? "GET":"POST"); + printf("PARAMS : %s\n",_param.c_str()); + + if (_targetFileName.size() == 0 ) { + _targetFileName = format("prepare%d",_uid); + } + + emscripten_async_wget2(url.c_str(), _targetFileName.c_str(), (_request==REQUEST_GET) ? "GET":"POST", _param.c_str(), this, http::onLoaded, http::onError, http::onProgress); + + } else { + _error = format("malformed url : %s\n",url.c_str()); + _content = ""; + _status = ST_FAILED; + _progressValue = -1; + } +} + +/** +* Accede a la reponse +*/ +const char* http::getContent() { + return _content.c_str(); +} + +/** +* Accede a l'erreur +*/ +const char* http::getError() { + return _error.c_str(); +} + +/** +* Accede au status +*/ +int http::getStatus() { + return _status; +} + +/** +* Accede a la progression between 0 & 100 +*/ +float http::getProgress() { + return (float)_progressValue; +} + +/** +* Accede a la progression between 0 & 100 +*/ +int http::getId() { + return _uid; +} + +/** +* Post +*/ +void http::addValue(const char* key, const char* value) { + if (_param.size() > 0) { + _param += "&"; + _param += key; + _param += "="; + _param += value; + } else { + _param += key; + _param += "="; + _param += value; + } +} + +void http::onProgress(int progress) { + _progressValue = progress; +} + +void http::onLoaded(const char* file) { + + if (strstr(file,"prepare")) { + FILE* f = fopen(file,"rb"); + if (f) { + fseek (f, 0, SEEK_END); + int size=ftell (f); + fseek (f, 0, SEEK_SET); + + char* data = new char[size]; + fread(data,size,1,f); + _content = data; + delete data; + fclose(f); + } else { + _content = file; + } + + } else { + _content = file; + } + + _progressValue = 100; + _status = ST_OK; +} + +void http::onError(int error) { + + printf("Error status : %d\n",error); + + _error = ""; + _content = ""; + _status = ST_FAILED; + _progressValue = -1; +} + +/// TEST +int num_request = 0; +float time_elapsed = 0.0f; + +void wait_https() { + if (num_request == 0) { + printf("End of all download ... %fs\n",(emscripten_get_now() - time_elapsed) / 1000.f); + emscripten_cancel_main_loop(); + int result = 0; + REPORT_RESULT(); + } +} + +void wait_http(void* request) { + http* req = reinterpret_cast(request); + if (req != 0) { + if (req->getStatus() == http::ST_PENDING) { + if ((int)req->getProgress()>0) { + printf("Progress Request n°%d : %d\n",req->getId(),(int)req->getProgress()); + } + emscripten_async_call(wait_http,request,500); + + } else { + if (req->getStatus() == http::ST_OK) { + printf("Success Request n°%d : %s\n",req->getId(),req->getContent()); + + } else { + printf("Error Request n°%d : %s\n",req->getId(), req->getError()); + } + + num_request --; + } + } else { + num_request --; + } +} + + +int main() { + time_elapsed = emscripten_get_now(); + + http* http1 = new http("https://github.com",http::REQUEST_GET,"emscripten_master.zip"); + http1->runRequest("/kripken/emscripten/archive/master.zip",http::ASSYNC_THREAD); + + http* http2 = new http("https://github.com",http::REQUEST_GET,"wolfviking_master.zip"); + http2->runRequest("/wolfviking0/image.js/archive/master.zip",http::ASSYNC_THREAD); + + http* http3 = new http("https://raw.github.com",http::REQUEST_GET); + http3->runRequest("/kripken/emscripten/master/LICENSE",http::ASSYNC_THREAD); + + num_request ++; + emscripten_async_call(wait_http,http1,500); + num_request ++; + emscripten_async_call(wait_http,http2,500); + num_request ++; + emscripten_async_call(wait_http,http3,500); + + /* + Http* http4 = new Http("http://www.---.com",Http::REQUEST_POST); + http4->addValue("app","123"); + http4->runRequest("/test.php",Http::ASSYNC_THREAD); + num_request ++; + emscripten_async_call(wait_http,http4,500); + */ + + emscripten_set_main_loop(wait_https, 0, 0); +} diff --git a/tests/http.h b/tests/http.h new file mode 100644 index 0000000000000..7eff701360191 --- /dev/null +++ b/tests/http.h @@ -0,0 +1,151 @@ +// +// Http.h +// Player Javascript +// +// Created by Anthony Liot on 23/11/12. +// + +#ifndef __HTTP_H__ +#define __HTTP_H__ + +#include + + +/* + */ +class http { + + public: + + enum Status { + ST_PENDING = 0, + ST_FAILED, + ST_OK + }; + + enum RequestType { + REQUEST_GET = 0, + REQUEST_POST , + }; + + enum AssyncMode { + ASSYNC_THREAD + }; + + // enregistrement sur unigine + static void RegisterAsExtension(bool regis); + + // Callback + static void onLoaded(void* parent, const char * file); + static void onError(void* parent, int statuserror); + static void onProgress(void* parent, int progress); + + // Constructeur + http(const char* hostname, int requestType, const char* targetFileName = ""); + + //Destructeur + virtual ~http(); + + /** + * Effectue la requete + */ + void runRequest(const char* page, int assync); + + /** + * Accede a la reponse + */ + const char* getContent(); + + /** + * Accede a l'erreur + */ + const char* getError(); + + /** + * Accede au status + */ + int getStatus(); + + /** + * Accede a la progression + */ + float getProgress(); + + /** + * Get Id of http Class + */ + int getId(); + + /** + * + */ + void addValue(const char* key, const char* value); + + /** + * Callback + */ + void onProgress(int progress); + void onLoaded(const char* file); + void onError(int error); + + // Static parameter + static int uid; + static std::string cross_domain ; + + private: + + // Id of request + int _uid; + + // nom de l'hote + std::string _hostname; + + // nom de la page + std::string _page; + + // target filename + std::string _targetFileName; + + // param + std::string _param; + + // resultat + std::string _content; + + // probleme + std::string _error; + + // request type + RequestType _request; + + // status + int _status; + + // progress value + int _progressValue; + + // mode assyncrone courant + AssyncMode _assync; + +}; + +//this is safe and convenient but not exactly efficient +inline std::string format(const char* fmt, ...){ + int size = 512; + char* buffer = 0; + buffer = new char[size]; + va_list vl; + va_start(vl,fmt); + int nsize = vsnprintf(buffer,size,fmt,vl); + if(size<=nsize){//fail delete buffer and try again + delete buffer; buffer = 0; + buffer = new char[nsize+1];//+1 for /0 + nsize = vsnprintf(buffer,size,fmt,vl); + } + std::string ret(buffer); + va_end(vl); + delete buffer; + return ret; +} + +#endif /* __HTTP_H__ */ diff --git a/tests/runner.py b/tests/runner.py index 742ddf1f856ca..9c903f20e15d3 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -2275,6 +2275,8 @@ def test_longjmp4(self): def test_exceptions(self): if Settings.QUANTUM_SIZE == 1: return self.skip("we don't support libcxx in q1") + Settings.EXCEPTION_DEBUG = 1 + self.banned_js_engines = [NODE_JS] # node issue 1669, exception causes stdout not to be flushed Settings.DISABLE_EXCEPTION_CATCHING = 0 if self.emcc_args is None: @@ -8927,6 +8929,8 @@ def test_js_optimizer(self): ['eliminateAsm']), (path_from_root('tools', 'test-js-optimizer-asm-regs.js'), open(path_from_root('tools', 'test-js-optimizer-asm-regs-output.js')).read(), ['registerizeAsm']), + (path_from_root('tools', 'test-js-optimizer-asm-pre.js'), open(path_from_root('tools', 'test-js-optimizer-asm-pre-output.js')).read(), + ['simplifyExpressionsPreAsm']), ]: output = Popen([NODE_JS, path_from_root('tools', 'js-optimizer.js'), input] + passes, stdin=PIPE, stdout=PIPE).communicate()[0] self.assertIdentical(expected, output.replace('\r\n', '\n').replace('\n\n', '\n')) @@ -10236,6 +10240,9 @@ def test_worker_api_2(self): Popen([PYTHON, EMCC, path_from_root('tests', 'worker_api_2_worker.cpp'), '-o', 'worker.js', '-s', 'BUILD_AS_WORKER=1', '-O2', '--minify', '0', '-s', 'EXPORTED_FUNCTIONS=["_one", "_two", "_three", "_four"]']).communicate() self.btest('worker_api_2_main.cpp', args=['-O2', '--minify', '0'], expected='11') + def test_emscripten_async_wget2(self): + self.btest('http.cpp', expected='0', args=['-I' + path_from_root('tests')]) + pids_to_clean = [] def clean_pids(self): import signal, errno @@ -10315,7 +10322,7 @@ def __exit__(self, *args, **kwargs): # always run these tests last # make sure to use different ports in each one because it takes a while for the processes to be cleaned up - def test_zz_websockets(self): + def test_websockets(self): try: with self.WebsockHarness(8990): self.btest('websockets.c', expected='571') @@ -10330,7 +10337,7 @@ def relay_server(q): proc.communicate() return relay_server - def test_zz_websockets_bi(self): + def test_websockets_bi(self): for datagram in [0,1]: try: with self.WebsockHarness(8992, self.make_relay_server(8992, 8994)): @@ -10340,7 +10347,7 @@ def test_zz_websockets_bi(self): finally: self.clean_pids() - def test_zz_websockets_bi_listen(self): + def test_websockets_bi_listen(self): try: with self.WebsockHarness(6992, self.make_relay_server(6992, 6994)): with self.WebsockHarness(6994, no_server=True): @@ -10349,14 +10356,14 @@ def test_zz_websockets_bi_listen(self): finally: self.clean_pids() - def test_zz_websockets_gethostbyname(self): + def test_websockets_gethostbyname(self): try: with self.WebsockHarness(7000): self.btest('websockets_gethostbyname.c', expected='571', args=['-O2']) finally: self.clean_pids() - def test_zz_websockets_bi_bigdata(self): + def test_websockets_bi_bigdata(self): try: with self.WebsockHarness(3992, self.make_relay_server(3992, 3994)): with self.WebsockHarness(3994, no_server=True): @@ -10365,7 +10372,7 @@ def test_zz_websockets_bi_bigdata(self): finally: self.clean_pids() - def test_zz_enet(self): + def test_enet(self): try_delete(self.in_dir('enet')) shutil.copytree(path_from_root('tests', 'enet'), self.in_dir('enet')) pwd = os.getcwd() @@ -10489,6 +10496,8 @@ def do_benchmark(self, name, src, args=[], expected_output='FAIL', emcc_args=[]) for i in range(TEST_REPS): start = time.time() js_output = self.run_generated_code(JS_ENGINE, final_filename, args, check_timeout=False) + if i == 0 and 'Successfully compiled asm.js code' in js_output: + print "[%s was asm.js'ified]" % name curr = time.time()-start times.append(curr) total_times[tests_done] += curr diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 103fb1fe9dbd5..ed9aa46d0698d 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -404,7 +404,7 @@ function removeUnneededLabelSettings(ast) { // Various expression simplifications. Pre run before closure (where we still have metadata), Post run after. -function simplifyExpressionsPre(ast) { +function simplifyExpressionsPre(ast, asm) { // When there is a bunch of math like (((8+5)|0)+12)|0, only the external |0 is needed, one correction is enough. // At each node, ((X|0)+Y)|0 can be transformed into (X+Y): The inner corrections are not needed // TODO: Is the same is true for 0xff, 0xffff? @@ -418,20 +418,31 @@ function simplifyExpressionsPre(ast) { var rerun = true; while (rerun) { rerun = false; - traverseGenerated(ast, function(node, type, stack) { - if (type == 'binary' && node[1] == '|' && (jsonCompare(node[2], ZERO) || jsonCompare(node[3], ZERO))) { - stack.push(1); // From here on up, no need for this kind of correction, it's done at the top - - // We might be able to remove this correction - for (var i = stack.length-2; i >= 0; i--) { - if (stack[i] == 1) { - // Great, we can eliminate - rerun = true; - return jsonCompare(node[2], ZERO) ? node[3] : node[2]; - } else if (stack[i] == -1) { - break; // Too bad, we can't + traverseGenerated(ast, function process(node, type, stack) { + if (type == 'binary' && node[1] == '|') { + if (node[2][0] == 'num' && node[3][0] == 'num') { + return ['num', node[2][1] | node[3][1]]; + } else if (jsonCompare(node[2], ZERO) || jsonCompare(node[3], ZERO)) { + // We might be able to remove this correction + for (var i = stack.length-1; i >= 0; i--) { + if (stack[i] == 1) { + // we will replace ourselves with the non-zero side. Recursively process that node. + var result = jsonCompare(node[2], ZERO) ? node[3] : node[2], other; + // Great, we can eliminate + rerun = true; + while (other = process(result, result[0], stack)) { + result = other; + } + return result; + } else if (stack[i] == -1) { + break; // Too bad, we can't + } else if (asm) { + break; // we must keep a coercion right on top of a heap access in asm mode + } } } + stack.push(1); // From here on up, no need for this kind of correction, it's done at the top + // (Add this at the end, so it is only added if we did not remove it) } else if (type == 'binary' && node[1] in USEFUL_BINARY_OPS) { stack.push(1); } else if ((type == 'binary' && node[1] in SAFE_BINARY_OPS) || type == 'num' || type == 'name') { @@ -445,6 +456,7 @@ function simplifyExpressionsPre(ast) { // &-related optimizations traverseGenerated(ast, function(node, type) { if (type == 'binary' && node[1] == '&' && node[3][0] == 'num') { + if (node[2][0] == 'num') return ['num', node[2][1] & node[3][1]]; var input = node[2]; var amount = node[3][1]; if (input[0] == 'binary' && input[1] == '&' && input[3][0] == 'num') { @@ -517,6 +529,10 @@ function simplifyExpressionsPre(ast) { // simplifyZeroComp(ast); TODO: investigate performance } +function simplifyExpressionsPreAsm(ast) { + simplifyExpressionsPre(ast, true); +} + // In typed arrays mode 2, we can have // HEAP[x >> 2] // very often. We can in some cases do the shift on the variable itself when it is set, @@ -1389,6 +1405,7 @@ function registerize(ast, asm) { // We also mark local variables - i.e., having a var definition var localVars = {}; var hasSwitch = false; // we cannot optimize variables if there is a switch + var hasReturnValue = false; traverse(fun, function(node, type) { if (type == 'var') { node[1].forEach(function(defined) { localVars[defined[0]] = 1 }); @@ -1400,6 +1417,8 @@ function registerize(ast, asm) { } } else if (type == 'switch') { hasSwitch = true; + } else if (asm && type == 'return' && node[1]) { + hasReturnValue = true; } }); vacuum(fun); @@ -1576,6 +1595,15 @@ function registerize(ast, asm) { } } denormalizeAsm(fun, finalAsmData); + // Add a final return if one is missing. This is not strictly a register operation, but + // this pass traverses the entire AST anyhow so adding it here is efficient. + if (hasReturnValue) { + var stats = getStatements(fun); + var last = stats[stats.length-1]; + if (last[0] != 'return') { + stats.push(['return', ['num', 0]]); + } + } } }); } @@ -2129,6 +2157,7 @@ var passes = { removeAssignsToUndefined: removeAssignsToUndefined, //removeUnneededLabelSettings: removeUnneededLabelSettings, simplifyExpressionsPre: simplifyExpressionsPre, + simplifyExpressionsPreAsm: simplifyExpressionsPreAsm, optimizeShiftsConservative: optimizeShiftsConservative, optimizeShiftsAggressive: optimizeShiftsAggressive, simplifyExpressionsPost: simplifyExpressionsPost, diff --git a/tools/shared.py b/tools/shared.py index 1b97e16687b29..a78db8e018d46 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -172,7 +172,7 @@ def check_node_version(): # 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.2.2' +EMSCRIPTEN_VERSION = '1.2.3' def check_sanity(force=False): try: diff --git a/tools/test-js-optimizer-asm-pre-output.js b/tools/test-js-optimizer-asm-pre-output.js new file mode 100644 index 0000000000000..afd43893fb56a --- /dev/null +++ b/tools/test-js-optimizer-asm-pre-output.js @@ -0,0 +1,8 @@ +function a() { + f((HEAPU8[10202] | 0) + 5 | 0); + f(HEAPU8[10202] | 0); + f(347); + f(351); + f(8); +} + diff --git a/tools/test-js-optimizer-asm-pre.js b/tools/test-js-optimizer-asm-pre.js new file mode 100644 index 0000000000000..6c9e64c1dc6ca --- /dev/null +++ b/tools/test-js-optimizer-asm-pre.js @@ -0,0 +1,8 @@ +function a() { + f((HEAPU8[10202] | 0) + 5 | 0); + f((HEAPU8[10202] | 0) | 0); + f(347 | 0); + f(347 | 12); + f(347 & 12); +} +// EMSCRIPTEN_GENERATED_FUNCTIONS: ["a"] diff --git a/tools/test-js-optimizer-asm-regs-output.js b/tools/test-js-optimizer-asm-regs-output.js index d9aa5c0cc66ac..26d1d134bc61f 100644 --- a/tools/test-js-optimizer-asm-regs-output.js +++ b/tools/test-js-optimizer-asm-regs-output.js @@ -18,4 +18,17 @@ function _doit(i1, i2, i3) { STACKTOP = i1; return 0 | 0; } +function rett() { + if (f()) { + g(); + return 5; + } + return 0; +} +function ret2t() { + if (f()) { + g(); + return; + } +} diff --git a/tools/test-js-optimizer-asm-regs.js b/tools/test-js-optimizer-asm-regs.js index 6ae7f7d091578..9192f32e31b67 100644 --- a/tools/test-js-optimizer-asm-regs.js +++ b/tools/test-js-optimizer-asm-regs.js @@ -20,5 +20,19 @@ function _doit($x, $y$0, $y$1) { STACKTOP = __stackBase__; return 0 | 0; } -// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "_doit"] +function rett() { + if (f()) { + g(); + return 5; + } + // missing final return, need to add it +} +function ret2t() { + if (f()) { + g(); + return; + } + // missing final return, but no need +} +// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "_doit", "rett", "ret2t"] diff --git a/tools/test-js-optimizer-output.js b/tools/test-js-optimizer-output.js index c95a36ff9afc7..5c06475e87608 100644 --- a/tools/test-js-optimizer-output.js +++ b/tools/test-js-optimizer-output.js @@ -290,5 +290,8 @@ function asmy() { f((HEAPU8[_buf + i6 & 16777215] & 1) + i5 | 0); f((HEAP8[_buf + i6 & 16777215] & 1) + i5 | 0); f((HEAPU8[_buf + i6 & 16777215] & 1) + i5 | 0); + if ((_sbrk($419 | 0) | 0) == -1) { + print("fleefl"); + } } diff --git a/tools/test-js-optimizer.js b/tools/test-js-optimizer.js index 00d6c2600cad7..982e3230e5b01 100644 --- a/tools/test-js-optimizer.js +++ b/tools/test-js-optimizer.js @@ -400,5 +400,8 @@ function asmy() { f((HEAPU8[_buf + i6 & 16777215] & 255 & 1) + i5 | 0); f((HEAP8[_buf + i6 & 16777215] & 1 & 255) + i5 | 0); f((HEAPU8[_buf + i6 & 16777215] & 1 & 255) + i5 | 0); + if ((_sbrk($419 | 0) | 0 | 0) == -1) { + print('fleefl'); + } } // EMSCRIPTEN_GENERATED_FUNCTIONS: ["abc", "xyz", "xyz2", "expr", "loopy", "bits", "maths", "hoisting", "demangle", "lua", "moreLabels", "notComps", "tricky", "asmy"]