From af4716bb662651c26fdbe5ca1fb1e6a4e8384301 Mon Sep 17 00:00:00 2001 From: Anthony Liot Date: Thu, 13 Dec 2012 15:27:37 +0100 Subject: [PATCH 01/22] Add emscripten_async_wget2 with progress callback Send request Post & Get with progress callback Add sample HTTP using emscripten_async_wget2 --- src/library_browser.js | 50 +++++ system/include/emscripten/emscripten.h | 2 + tests/http.cpp | 284 +++++++++++++++++++++++++ tests/http.h | 151 +++++++++++++ 4 files changed, 487 insertions(+) create mode 100644 tests/http.cpp create mode 100644 tests/http.h diff --git a/src/library_browser.js b/src/library_browser.js index 00ee158c29a8e..c18edf4a06292 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -385,6 +385,56 @@ mergeInto(LibraryManager.library, { ); }, + 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); + }; + + 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/system/include/emscripten/emscripten.h b/system/include/emscripten/emscripten.h index 3eefe0b876809..fb63384b0e449 100644 --- a/system/include/emscripten/emscripten.h +++ b/system/include/emscripten/emscripten.h @@ -186,6 +186,8 @@ float emscripten_random(); */ void emscripten_async_wget(const char* url, const char* file, void (*onload)(const char*), void (*onerror)(const char*)); +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 diff --git a/tests/http.cpp b/tests/http.cpp new file mode 100644 index 0000000000000..623709bf6150b --- /dev/null +++ b/tests/http.cpp @@ -0,0 +1,284 @@ +// +// 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(); + } +} + +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); +} \ No newline at end of file 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__ */ From 68d5248ff7152368c95a861560ecbb5c734ab1d7 Mon Sep 17 00:00:00 2001 From: Anthony Liot Date: Fri, 28 Dec 2012 09:36:13 +0100 Subject: [PATCH 02/22] Add comment,test,authors. Change indentation and comma space Modify the indentation Add Author Add comment Add test inside runner.py --- AUTHORS | 2 +- src/library_browser.js | 51 +++++++++++++------------- system/include/emscripten/emscripten.h | 13 +++++++ tests/runner.py | 4 ++ 4 files changed, 43 insertions(+), 27 deletions(-) diff --git a/AUTHORS b/AUTHORS index 264f1e4c84fb6..c1f93e2cd19a9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -42,4 +42,4 @@ a license to everyone to use it as detailed in LICENSE.) * Manuel Wellmann (copyright owned by Autodesk, Inc.) * Xuejie Xiao * Dominic Wong - +* Anthony Liot diff --git a/src/library_browser.js b/src/library_browser.js index c18edf4a06292..0525dd49702cd 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -394,45 +394,44 @@ mergeInto(LibraryManager.library, { var http = new XMLHttpRequest(); http.open(_request, _url, true); - http.responseType = 'arraybuffer'; + 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); - } + 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); - }; + 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); - }; + // PROGRESS + http.onprogress = function(e) { + var percentComplete = (e.position / e.totalSize)*100; + if (onprogress) FUNCTION_TABLE[onprogress](arg, percentComplete); + }; - try { - if (http.channel instanceof Ci.nsIHttpChannel) - http.channel.redirectionLimit = 0; - } catch (ex) { /* whatever */ } + // 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); + //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); + http.send(null); } - }, emscripten_async_prepare: function(file, onload, onerror) { diff --git a/system/include/emscripten/emscripten.h b/system/include/emscripten/emscripten.h index fb63384b0e449..30165d8b8ebe2 100644 --- a/system/include/emscripten/emscripten.h +++ b/system/include/emscripten/emscripten.h @@ -186,6 +186,19 @@ float emscripten_random(); */ void emscripten_async_wget(const char* url, const char* file, void (*onload)(const char*), void (*onerror)(const char*)); +/* + * Load file from url in asynchronous way. + * 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)); /* diff --git a/tests/runner.py b/tests/runner.py index 775225c682992..f04413cf549b5 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -10005,6 +10005,10 @@ 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): + Popen(['python', EMCC, path_from_root('tests', 'http.cpp'), '-o', 'http.html', '-I' + path_from_root('tests')]).communicate() + self.btest('http.cpp', expected='0', args=['-I' + path_from_root('tests')]) + pids_to_clean = [] def clean_pids(self): import signal, errno From 0cb8bcd6a86d99ca371b8f1f9f817afc337aa4af Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 7 Jan 2013 18:50:53 -0800 Subject: [PATCH 03/22] wget2 docs --- system/include/emscripten/emscripten.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/system/include/emscripten/emscripten.h b/system/include/emscripten/emscripten.h index f87e49888e6a5..6b254ee62d789 100644 --- a/system/include/emscripten/emscripten.h +++ b/system/include/emscripten/emscripten.h @@ -212,8 +212,9 @@ 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. - * Load file from url in asynchronous way. + * 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. From 2d164ea8392eef8f863639cc71cff59c8305fc18 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Jan 2013 10:35:10 -0800 Subject: [PATCH 04/22] rename websockets tests --- tests/runner.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/runner.py b/tests/runner.py index 7c049ede4ee81..7a68070c9c2c1 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -10318,7 +10318,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') @@ -10333,7 +10333,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)): @@ -10343,7 +10343,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): @@ -10352,14 +10352,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): @@ -10368,7 +10368,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() From 828a7ec1348919be61e55c4d55c23b4a148072ac Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Jan 2013 11:05:45 -0800 Subject: [PATCH 05/22] refactor networking code to start supporting multiple backends --- src/library.js | 205 ++++++++++++++----------- system/include/emscripten/emscripten.h | 11 ++ tests/enet_client.c | 2 +- tests/enet_server.c | 2 +- 4 files changed, 126 insertions(+), 94 deletions(-) diff --git a/src/library.js b/src/library.js index 1e528d0668494..f4dd9ba77d76c 100644 --- a/src/library.js +++ b/src/library.js @@ -6779,8 +6779,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 +6802,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 +6917,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 +6943,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/system/include/emscripten/emscripten.h b/system/include/emscripten/emscripten.h index 6b254ee62d789..93551f39fecc6 100644 --- a/system/include/emscripten/emscripten.h +++ b/system/include/emscripten/emscripten.h @@ -321,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/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; From 3a866bd0b3dc95e030b6b04d6a3534d4ac8b57e6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Jan 2013 13:26:54 -0800 Subject: [PATCH 06/22] define tempInt in asm --- emscripten.py | 1 + 1 file changed, 1 insertion(+) diff --git a/emscripten.py b/emscripten.py index 204cbbec5af1e..8fa1c3e5d98ba 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; ''' + ''.join([''' var tempRet%d = 0;''' % i for i in range(10)]) + '\n' + asm_global_funcs + ''' function stackAlloc(size) { From 4379d19dc217376bb11e9c4172c84ac194409c3c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Jan 2013 13:51:02 -0800 Subject: [PATCH 07/22] ensure a return when an abort happens in a function that returns something --- src/jsifier.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/jsifier.js b/src/jsifier.js index 24af5b6ddc197..e57facbd8db0d 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -1329,6 +1329,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; } From a4a85b7c87020e0b306c62a5c91c37641cb8ef7e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Jan 2013 14:10:05 -0800 Subject: [PATCH 08/22] fix test_exceptions --- tests/runner.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/runner.py b/tests/runner.py index 7a68070c9c2c1..a6508fe935c49 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: From 166aed3b7bc41ef066c6477c97533ce36a2ff977 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Jan 2013 14:13:03 -0800 Subject: [PATCH 09/22] stub _ZNSt9exceptionD2Ev for the case where dlmalloc is included but not libcxx, to avoid an asm warning --- src/library.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/library.js b/src/library.js index f4dd9ba77d76c..8575828690dac 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. From 14bb76d546b774bf823e3a578aa69f7d1bff0f2c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Jan 2013 14:17:30 -0800 Subject: [PATCH 10/22] asm coerce varargs argument --- src/jsifier.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jsifier.js b/src/jsifier.js index e57facbd8db0d..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); From 486db79e93244b743b8ce8bef83ce93ef9470e16 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Jan 2013 15:12:12 -0800 Subject: [PATCH 11/22] fix bug with lack of recursion in simplifyBitops --- tools/js-optimizer.js | 15 ++++++++++----- tools/test-js-optimizer-output.js | 3 +++ tools/test-js-optimizer.js | 3 +++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 103fb1fe9dbd5..22d0f4d1a54ee 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -418,20 +418,25 @@ function simplifyExpressionsPre(ast) { var rerun = true; while (rerun) { rerun = false; - traverseGenerated(ast, function(node, type, stack) { + traverseGenerated(ast, function process(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--) { + for (var i = stack.length-1; i >= 0; i--) { if (stack[i] == 1) { // Great, we can eliminate rerun = true; - return jsonCompare(node[2], ZERO) ? node[3] : node[2]; + // we will replace ourselves with the non-zero side. Recursively process that node. + var result = jsonCompare(node[2], ZERO) ? node[3] : node[2], other; + while (other = process(result, result[0], stack)) { + result = other; + } + return result; } else if (stack[i] == -1) { break; // Too bad, we can't } } + 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') { 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"] From e30b32a8ca2ee129166e59b93ebec17f5063bab2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Jan 2013 15:57:40 -0800 Subject: [PATCH 12/22] uncomment exception stub --- src/library.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library.js b/src/library.js index 8575828690dac..82e42a28e2d66 100644 --- a/src/library.js +++ b/src/library.js @@ -5104,7 +5104,7 @@ LibraryManager.library = { } }, -// _ZNSt9exceptionD2Ev: function(){}, // XXX a dependency of dlmalloc, but not actually needed if libcxx is not anyhow included + _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 From 0779c55c28f17d796f3f13962cfcac954e6cef59 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Jan 2013 16:03:01 -0800 Subject: [PATCH 13/22] fix asmCoercion for aggregate types --- src/parseTools.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/parseTools.js b/src/parseTools.js index 89267d102681e..d22f44151d51f 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -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)'; } } From 78dbafb289e222550abda6b53e7099352a599804 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Jan 2013 16:41:44 -0800 Subject: [PATCH 14/22] keep a coercion right on top of heap accesses in asm mode --- emcc | 9 +++++++-- tests/runner.py | 2 ++ tools/js-optimizer.js | 12 +++++++++--- tools/test-js-optimizer-asm-pre-output.js | 4 ++++ tools/test-js-optimizer-asm-pre.js | 4 ++++ 5 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 tools/test-js-optimizer-asm-pre-output.js create mode 100644 tools/test-js-optimizer-asm-pre.js diff --git a/emcc b/emcc index 7c971b71dd871..9483ef6482e07 100755 --- a/emcc +++ b/emcc @@ -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/tests/runner.py b/tests/runner.py index a6508fe935c49..d5662375ae925 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -8929,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')) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 22d0f4d1a54ee..7fe8d99ff9847 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? @@ -423,10 +423,11 @@ function simplifyExpressionsPre(ast) { // We might be able to remove this correction for (var i = stack.length-1; i >= 0; i--) { if (stack[i] == 1) { - // Great, we can eliminate - rerun = true; // we will replace ourselves with the non-zero side. Recursively process that node. var result = jsonCompare(node[2], ZERO) ? node[3] : node[2], other; + if (asm && result[0] == 'sub') break; // we must keep a coercion right on top of a heap access in asm mode + // Great, we can eliminate + rerun = true; while (other = process(result, result[0], stack)) { result = other; } @@ -522,6 +523,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, @@ -2134,6 +2139,7 @@ var passes = { removeAssignsToUndefined: removeAssignsToUndefined, //removeUnneededLabelSettings: removeUnneededLabelSettings, simplifyExpressionsPre: simplifyExpressionsPre, + simplifyExpressionsPreAsm: simplifyExpressionsPreAsm, optimizeShiftsConservative: optimizeShiftsConservative, optimizeShiftsAggressive: optimizeShiftsAggressive, simplifyExpressionsPost: simplifyExpressionsPost, 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..102124702e16d --- /dev/null +++ b/tools/test-js-optimizer-asm-pre-output.js @@ -0,0 +1,4 @@ +function a() { + f((HEAPU8[10202] | 0) + 5 | 0); +} + diff --git a/tools/test-js-optimizer-asm-pre.js b/tools/test-js-optimizer-asm-pre.js new file mode 100644 index 0000000000000..8d33a6a2c93f3 --- /dev/null +++ b/tools/test-js-optimizer-asm-pre.js @@ -0,0 +1,4 @@ +function a() { + f((HEAPU8[10202] | 0) + 5 | 0); +} +// EMSCRIPTEN_GENERATED_FUNCTIONS: ["a"] From c31a515ee32a2aef2194e09e3f977c6961fce410 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Jan 2013 16:50:37 -0800 Subject: [PATCH 15/22] improve |0 removal in asm mode a little --- tools/js-optimizer.js | 3 ++- tools/test-js-optimizer-asm-pre-output.js | 1 + tools/test-js-optimizer-asm-pre.js | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 7fe8d99ff9847..a8753e6f730ac 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -425,7 +425,6 @@ function simplifyExpressionsPre(ast, asm) { 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; - if (asm && result[0] == 'sub') break; // we must keep a coercion right on top of a heap access in asm mode // Great, we can eliminate rerun = true; while (other = process(result, result[0], stack)) { @@ -434,6 +433,8 @@ function simplifyExpressionsPre(ast, asm) { 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 diff --git a/tools/test-js-optimizer-asm-pre-output.js b/tools/test-js-optimizer-asm-pre-output.js index 102124702e16d..6d380567e6728 100644 --- a/tools/test-js-optimizer-asm-pre-output.js +++ b/tools/test-js-optimizer-asm-pre-output.js @@ -1,4 +1,5 @@ function a() { f((HEAPU8[10202] | 0) + 5 | 0); + f(HEAPU8[10202] | 0); } diff --git a/tools/test-js-optimizer-asm-pre.js b/tools/test-js-optimizer-asm-pre.js index 8d33a6a2c93f3..71969cc3d18de 100644 --- a/tools/test-js-optimizer-asm-pre.js +++ b/tools/test-js-optimizer-asm-pre.js @@ -1,4 +1,5 @@ function a() { f((HEAPU8[10202] | 0) + 5 | 0); + f((HEAPU8[10202] | 0) | 0); } // EMSCRIPTEN_GENERATED_FUNCTIONS: ["a"] From 62ca2bf2fa47370ec845c91bb2c2e73d48d2c4f5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Jan 2013 16:52:02 -0800 Subject: [PATCH 16/22] add tempValue in asm --- emscripten.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emscripten.py b/emscripten.py index 8fa1c3e5d98ba..6b990bd37370c 100755 --- a/emscripten.py +++ b/emscripten.py @@ -377,7 +377,7 @@ def make_table(sig, raw): ''' % (asm_setup,) + '\n' + asm_global_vars + ''' var __THREW__ = 0; var undef = 0; - var tempInt = 0; + var tempInt = 0, tempValue = 0; ''' + ''.join([''' var tempRet%d = 0;''' % i for i in range(10)]) + '\n' + asm_global_funcs + ''' function stackAlloc(size) { From ed106fefa47ea6d156d8ab9cad2dd479e853acbe Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Jan 2013 17:15:36 -0800 Subject: [PATCH 17/22] make sure there is a final return in functions that return value in asm --- tools/js-optimizer.js | 12 ++++++++++++ tools/test-js-optimizer-asm-regs-output.js | 13 +++++++++++++ tools/test-js-optimizer-asm-regs.js | 16 +++++++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index a8753e6f730ac..446ca72389f16 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -1400,6 +1400,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 }); @@ -1411,6 +1412,8 @@ function registerize(ast, asm) { } } else if (type == 'switch') { hasSwitch = true; + } else if (asm && type == 'return' && node[1]) { + hasReturnValue = true; } }); vacuum(fun); @@ -1587,6 +1590,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]]); + } + } } }); } 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"] From 4c4caded645576f4cfe2c9088eaba09b6d731f90 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Jan 2013 17:59:58 -0800 Subject: [PATCH 18/22] make docs mention that bitcode is the default --- emcc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/emcc b/emcc index 9483ef6482e07..7c4291ca7405c 100755 --- a/emcc +++ b/emcc @@ -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 From d7c3e10b670e35866b6a50865a0baad225280aeb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Jan 2013 18:24:29 -0800 Subject: [PATCH 19/22] optimize |,& on constants --- tools/js-optimizer.js | 35 +++++++++++++---------- tools/test-js-optimizer-asm-pre-output.js | 3 ++ tools/test-js-optimizer-asm-pre.js | 3 ++ 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 446ca72389f16..ed9aa46d0698d 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -419,22 +419,26 @@ function simplifyExpressionsPre(ast, asm) { while (rerun) { rerun = false; traverseGenerated(ast, function process(node, type, stack) { - if (type == 'binary' && node[1] == '|' && (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; + 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 } - 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 @@ -452,6 +456,7 @@ function simplifyExpressionsPre(ast, asm) { // &-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') { diff --git a/tools/test-js-optimizer-asm-pre-output.js b/tools/test-js-optimizer-asm-pre-output.js index 6d380567e6728..afd43893fb56a 100644 --- a/tools/test-js-optimizer-asm-pre-output.js +++ b/tools/test-js-optimizer-asm-pre-output.js @@ -1,5 +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 index 71969cc3d18de..6c9e64c1dc6ca 100644 --- a/tools/test-js-optimizer-asm-pre.js +++ b/tools/test-js-optimizer-asm-pre.js @@ -1,5 +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"] From b6c33b1fb56211577defda3509129e2395128280 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Jan 2013 10:55:36 -0800 Subject: [PATCH 20/22] fix asmEnsureFloat and asmInitializer --- src/parseTools.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/parseTools.js b/src/parseTools.js index d22f44151d51f..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'; } } From 4dd19ff1149a9602783c998260c24729e64f7d24 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Jan 2013 11:09:59 -0800 Subject: [PATCH 21/22] mention asm.js'ing in benchmarks --- tests/runner.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/runner.py b/tests/runner.py index d5662375ae925..9c903f20e15d3 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -10496,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 From 8e283ee92b501aff711d28063db263a63cc1071a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Jan 2013 12:38:43 -0800 Subject: [PATCH 22/22] rename dlmalloc to libc in cache, in preparation for adding further code there --- emcc | 28 +++++++++---------- system/lib/{dlmalloc.symbols => libc.symbols} | 0 tools/shared.py | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) rename system/lib/{dlmalloc.symbols => libc.symbols} (100%) diff --git a/emcc b/emcc index 7c4291ca7405c..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 @@ -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: 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/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: