diff --git a/emcc.py b/emcc.py
index 3708ac8ee95e0..5047b41f77aa5 100755
--- a/emcc.py
+++ b/emcc.py
@@ -1967,7 +1967,8 @@ def do_minify(): # minifies the code. this is also when we do certain optimizati
if 'native-wasm' in shared.Settings.BINARYEN_METHOD or 'interpret-binary' in shared.Settings.BINARYEN_METHOD:
logging.debug('wasm-as (wasm => binary)')
subprocess.check_call([os.path.join(binaryen_bin, 'wasm-as'), wasm_text_target, '-o', wasm_binary_target])
- shutil.copyfile(wasm_text_target + '.mappedGlobals', wasm_binary_target + '.mappedGlobals')
+ if os.path.exists(wasm_text_target + '.mappedGlobals'): # TODO: remove once we no longer use .mappedGlobals files at all, as binaryen is moving to https://github.com/WebAssembly/binaryen/issues/675
+ shutil.copyfile(wasm_text_target + '.mappedGlobals', wasm_binary_target + '.mappedGlobals')
# If we were asked to also generate HTML, do that
if final_suffix == 'html':
@@ -2110,8 +2111,7 @@ def un_src(): # use this if you want to modify the script and need it to be inli
shutil.move(js_target, js_target[:-3] + '.worker.js') # compiler output goes in .worker.js file
worker_target_basename = target_basename + '.worker'
target_contents = open(shared.path_from_root('src', 'webGLClient.js')).read() + '\n' + open(shared.path_from_root('src', 'proxyClient.js')).read().replace('{{{ filename }}}', shared.Settings.PROXY_TO_WORKER_FILENAME or worker_target_basename).replace('{{{ IDBStore.js }}}', open(shared.path_from_root('src', 'IDBStore.js')).read())
- target_contents = tools.line_endings.convert_line_endings(convert_line_endings, '\n', output_eol)
- open(target, 'wb').write(target_contents)
+ open(target, 'w').write(target_contents)
for f in generated_text_files_with_native_eols:
tools.line_endings.convert_line_endings_in_file(f, os.linesep, output_eol)
diff --git a/emscripten-version.txt b/emscripten-version.txt
index 2a4be5f6e8e25..d873f1145a203 100644
--- a/emscripten-version.txt
+++ b/emscripten-version.txt
@@ -1,2 +1,2 @@
-"1.36.8"
+"1.36.9"
diff --git a/emscripten.py b/emscripten.py
index e0caff8dc90fd..25d046dd03a1b 100755
--- a/emscripten.py
+++ b/emscripten.py
@@ -651,7 +651,8 @@ def keyfunc(other):
'(it is worth building your source files with -Werror (warnings are errors), as warnings can indicate undefined behavior which can cause this)' + \
'"); ' + extra
- basic_funcs = ['abort', 'assert'] + [m.replace('.', '_') for m in math_envs]
+ basic_funcs = ['abort', 'assert', 'enlargeMemory', 'getTotalMemory'] + [m.replace('.', '_') for m in math_envs]
+ if settings['ABORTING_MALLOC']: basic_funcs += ['abortOnCannotGrowMemory']
if settings['STACK_OVERFLOW_CHECK']: basic_funcs += ['abortStackOverflow']
asm_safe_heap = settings['SAFE_HEAP'] and not settings['SAFE_HEAP_LOG'] and not settings['RELOCATABLE'] # optimized safe heap in asm, when we can
@@ -667,11 +668,9 @@ def keyfunc(other):
basic_funcs += ['nullFunc_' + sig]
asm_setup += '\nfunction nullFunc_' + sig + '(x) { ' + get_function_pointer_error(sig) + 'abort(x) }\n'
- basic_vars = ['STACKTOP', 'STACK_MAX', 'tempDoublePtr', 'ABORT']
+ basic_vars = ['STACKTOP', 'STACK_MAX', 'DYNAMICTOP_PTR', 'tempDoublePtr', 'ABORT']
basic_float_vars = []
- if settings['SAFE_HEAP']: basic_vars += ['DYNAMICTOP']
-
if metadata.get('preciseI64MathUsed'):
basic_vars += ['cttz_i8']
else:
@@ -1063,7 +1062,7 @@ def finalize_output(metadata, post, funcs_js, need_asyncify, provide_fround, asm
'''] + ['' if not settings['SAFE_HEAP'] else '''
function setDynamicTop(value) {
value = value | 0;
- DYNAMICTOP = value;
+ HEAP32[DYNAMICTOP_PTR>>2] = value;
}
'''] + ['' if not asm_safe_heap else '''
function SAFE_HEAP_STORE(dest, value, bytes) {
@@ -1071,7 +1070,7 @@ def finalize_output(metadata, post, funcs_js, need_asyncify, provide_fround, asm
value = value | 0;
bytes = bytes | 0;
if ((dest|0) <= 0) segfault();
- if (((dest + bytes)|0) > (DYNAMICTOP|0)) segfault();
+ if (((dest + bytes)|0) > (HEAP32[DYNAMICTOP_PTR>>2]|0)) segfault();
if ((bytes|0) == 4) {
if ((dest&3)) alignfault();
HEAP32[dest>>2] = value;
@@ -1087,7 +1086,7 @@ def finalize_output(metadata, post, funcs_js, need_asyncify, provide_fround, asm
value = +value;
bytes = bytes | 0;
if ((dest|0) <= 0) segfault();
- if (((dest + bytes)|0) > (DYNAMICTOP|0)) segfault();
+ if (((dest + bytes)|0) > (HEAP32[DYNAMICTOP_PTR>>2]|0)) segfault();
if ((bytes|0) == 8) {
if ((dest&7)) alignfault();
HEAPF64[dest>>3] = value;
@@ -1101,7 +1100,7 @@ def finalize_output(metadata, post, funcs_js, need_asyncify, provide_fround, asm
bytes = bytes | 0;
unsigned = unsigned | 0;
if ((dest|0) <= 0) segfault();
- if ((dest + bytes|0) > (DYNAMICTOP|0)) segfault();
+ if ((dest + bytes|0) > (HEAP32[DYNAMICTOP_PTR>>2]|0)) segfault();
if ((bytes|0) == 4) {
if ((dest&3)) alignfault();
return HEAP32[dest>>2] | 0;
@@ -1120,7 +1119,7 @@ def finalize_output(metadata, post, funcs_js, need_asyncify, provide_fround, asm
dest = dest | 0;
bytes = bytes | 0;
if ((dest|0) <= 0) segfault();
- if ((dest + bytes|0) > (DYNAMICTOP|0)) segfault();
+ if ((dest + bytes|0) > (HEAP32[DYNAMICTOP_PTR>>2]|0)) segfault();
if ((bytes|0) == 8) {
if ((dest&7)) alignfault();
return +HEAPF64[dest>>3];
@@ -1530,7 +1529,8 @@ def save_settings():
outfile.write(pre)
pre = None
- basic_funcs = ['abort', 'assert']
+ basic_funcs = ['abort', 'assert', 'enlargeMemory', 'getTotalMemory']
+ if settings['ABORTING_MALLOC']: basic_funcs += ['abortOnCannotGrowMemory']
access_quote = access_quoter(settings)
@@ -1547,7 +1547,7 @@ def save_settings():
def math_fix(g):
return g if not g.startswith('Math_') else g.split('_')[1]
- basic_vars = ['STACKTOP', 'STACK_MAX', 'ABORT']
+ basic_vars = ['STACKTOP', 'STACK_MAX', 'DYNAMICTOP_PTR', 'ABORT']
basic_float_vars = []
# Asm.js-style exception handling: invoke wrapper generation
diff --git a/site/source/docs/api_reference/preamble.js.rst b/site/source/docs/api_reference/preamble.js.rst
index 0830506e8f4a2..ac37350a017ba 100644
--- a/site/source/docs/api_reference/preamble.js.rst
+++ b/site/source/docs/api_reference/preamble.js.rst
@@ -166,14 +166,14 @@ Conversion functions — strings, pointers and arrays
-.. js:function:: stringToUTF8(str, outPtr[, maxBytesToWrite])
+.. js:function:: stringToUTF8(str, outPtr, maxBytesToWrite)
Copies the given JavaScript ``String`` object ``str`` to the Emscripten HEAP at address ``outPtr``, null-terminated and encoded in UTF8 form.
:param str: A JavaScript ``String`` object.
:type str: String
:param outPtr: Pointer to data copied from ``str``, encoded in UTF8 format and null-terminated.
- :param maxBytesToWrite: A limit on the number of bytes to write out.
+ :param maxBytesToWrite: A limit on the number of bytes that this function can at most write out. If the string is longer than this, the output is truncated. The outputted string will always be null terminated, even if truncation occurred.
.. js:function:: UTF16ToString(ptr)
@@ -185,7 +185,7 @@ Conversion functions — strings, pointers and arrays
-.. js:function:: stringToUTF16(str, outPtr[, maxBytesToWrite])
+.. js:function:: stringToUTF16(str, outPtr, maxBytesToWrite)
Copies the given JavaScript ``String`` object ``str`` to the Emscripten HEAP at address ``outPtr``, null-terminated and encoded in UTF16LE form.
@@ -194,7 +194,7 @@ Conversion functions — strings, pointers and arrays
:param str: A JavaScript ``String`` object.
:type str: String
:param outPtr: Pointer to data copied from ``str``, encoded in UTF16LE format and null-terminated.
- :param maxBytesToWrite: A limit on the number of bytes to write out.
+ :param maxBytesToWrite: A limit on the number of bytes that this function can at most write out. If the string is longer than this, the output is truncated. The outputted string will always be null terminated, even if truncation occurred.
@@ -206,7 +206,7 @@ Conversion functions — strings, pointers and arrays
:returns: A JavaScript ``String`` object.
-.. js:function:: stringToUTF32(str, outPtr[, maxBytesToWrite])
+.. js:function:: stringToUTF32(str, outPtr, maxBytesToWrite)
Copies the given JavaScript ``String`` object ``str`` to the Emscripten HEAP at address ``outPtr``, null-terminated and encoded in UTF32LE form.
@@ -215,7 +215,7 @@ Conversion functions — strings, pointers and arrays
:param str: A JavaScript ``String`` object.
:type str: String
:param outPtr: Pointer to data copied from ``str``, encoded in encoded in UTF32LE format and null-terminated.
- :param maxBytesToWrite: A limit on the number of bytes to write out.
+ :param maxBytesToWrite: A limit on the number of bytes that this function can at most write out. If the string is longer than this, the output is truncated. The outputted string will always be null terminated, even if truncation occurred.
diff --git a/src/deterministic.js b/src/deterministic.js
index da95835217d3e..e908dc5d94977 100644
--- a/src/deterministic.js
+++ b/src/deterministic.js
@@ -15,7 +15,7 @@ Module['thisProgram'] = 'thisProgram'; // for consistency between different buil
function hashMemory(id) {
var ret = 0;
- var len = Math.max(DYNAMICTOP, STATICTOP);
+ var len = Math.max(HEAP32[DYNAMICTOP_PTR>>2], STATICTOP);
for (var i = 0; i < len; i++) {
ret = (ret*17 + HEAPU8[i])|0;
}
diff --git a/src/jsifier.js b/src/jsifier.js
index 4d6fcf36a9539..b009a433b47aa 100644
--- a/src/jsifier.js
+++ b/src/jsifier.js
@@ -381,11 +381,15 @@ function JSify(data, functionsOnly) {
legalizedI64s = legalizedI64sDefault;
if (!BUILD_AS_SHARED_LIB && !SIDE_MODULE) {
+ if (USE_PTHREADS) print('if (!ENVIRONMENT_IS_PTHREAD) {\n // Only main thread initializes these, pthreads copy them over at thread worker init time (in pthread-main.js)');
+ print('DYNAMICTOP_PTR = allocate(1, "i32", ALLOC_STATIC);\n');
print('STACK_BASE = STACKTOP = Runtime.alignMemory(STATICTOP);\n');
- print('staticSealed = true; // seal the static portion of memory\n');
print('STACK_MAX = STACK_BASE + TOTAL_STACK;\n');
- print('DYNAMIC_BASE = DYNAMICTOP = Runtime.alignMemory(STACK_MAX);\n');
+ print('DYNAMIC_BASE = Runtime.alignMemory(STACK_MAX);\n');
+ print('HEAP32[DYNAMICTOP_PTR>>2] = DYNAMIC_BASE;\n');
+ print('staticSealed = true; // seal the static portion of memory\n');
if (ASSERTIONS) print('assert(DYNAMIC_BASE < TOTAL_MEMORY, "TOTAL_MEMORY not big enough for stack");\n');
+ if (USE_PTHREADS) print('}\n');
}
if (SPLIT_MEMORY) {
print('assert(STACK_MAX < SPLIT_MEMORY, "SPLIT_MEMORY size must be big enough so the entire static memory + stack can fit in one chunk, need " + STACK_MAX);\n');
diff --git a/src/library.js b/src/library.js
index ba81120a83ac8..c7c0a7fa7d190 100644
--- a/src/library.js
+++ b/src/library.js
@@ -428,28 +428,111 @@ LibraryManager.library = {
___setErrNo(ERRNO_CODES.EINVAL);
return -1;
},
- sbrk: function(bytes) {
+
+ // Implement a Linux-like 'memory area' for our 'process'.
+ // Changes the size of the memory area by |bytes|; returns the
+ // address of the previous top ('break') of the memory area
+ // We control the "dynamic" memory - DYNAMIC_BASE to DYNAMICTOP
+ sbrk__asm: true,
+ sbrk__sig: ['ii'],
+ sbrk__deps: ['__setErrNo'],
+ sbrk: function(increment) {
+ increment = increment|0;
+ var oldDynamicTop = 0;
+ var oldDynamicTopOnChange = 0;
+ var newDynamicTop = 0;
+ var totalMemory = 0;
+ increment = ((increment + 15) & -16)|0;
+#if USE_PTHREADS
+ totalMemory = getTotalMemory()|0;
+
+ // Perform a compare-and-swap loop to update the new dynamic top value. This is because
+ // this function can becalled simultaneously in multiple threads.
+ do {
+ oldDynamicTop = Atomics_load(HEAP32, DYNAMICTOP_PTR>>2);
+ newDynamicTop = oldDynamicTop + increment | 0;
+ // Asking to increase dynamic top to a too high value? In pthreads builds we cannot
+ // enlarge memory, so this needs to fail.
+ if (((increment|0) > 0 & (newDynamicTop|0) < (oldDynamicTop|0)) // Detect and fail if we would wrap around signed 32-bit int.
+ | (newDynamicTop|0) < 0 // Also underflow, sbrk() should be able to be used to subtract.
+ | (newDynamicTop|0) > (totalMemory|0)) {
+#if ABORTING_MALLOC
+ abortOnCannotGrowMemory()|0;
+#else
+ ___setErrNo({{{ cDefine('ENOMEM') }}});
+ return -1;
+#endif
+ }
+ // Attempt to update the dynamic top to new value. Another thread may have beat this thread to the update,
+ // in which case we will need to start over by iterating the loop body again.
+ oldDynamicTopOnChange = Atomics_compareExchange(HEAP32, DYNAMICTOP_PTR>>2, oldDynamicTop|0, newDynamicTop|0);
+ } while((oldDynamicTopOnChange|0) != (oldDynamicTop|0));
+#else // singlethreaded build: (-s USE_PTHREADS=0)
+ oldDynamicTop = HEAP32[DYNAMICTOP_PTR>>2]|0;
+ newDynamicTop = oldDynamicTop + increment | 0;
+
+ if (((increment|0) > 0 & (newDynamicTop|0) < (oldDynamicTop|0)) // Detect and fail if we would wrap around signed 32-bit int.
+ | (newDynamicTop|0) < 0) { // Also underflow, sbrk() should be able to be used to subtract.
+#if ABORTING_MALLOC
+ abortOnCannotGrowMemory()|0;
+#endif
+ ___setErrNo({{{ cDefine('ENOMEM') }}});
+ return -1;
+ }
+
+ HEAP32[DYNAMICTOP_PTR>>2] = newDynamicTop;
+ totalMemory = getTotalMemory()|0;
+ if ((newDynamicTop|0) > (totalMemory|0)) {
+ if ((enlargeMemory()|0) == 0) {
+ ___setErrNo({{{ cDefine('ENOMEM') }}});
+ HEAP32[DYNAMICTOP_PTR>>2] = oldDynamicTop;
+ return -1;
+ }
+ }
+#endif
+ return oldDynamicTop|0;
+ },
+
+ brk__asm: true,
+ brk__sig: ['ii'],
+ brk: function(newDynamicTop) {
+ newDynamicTop = newDynamicTop|0;
+ var oldDynamicTop = 0;
+ var totalMemory = 0;
#if USE_PTHREADS
- if (ENVIRONMENT_IS_PTHREAD) return _emscripten_sync_run_in_main_thread_1({{{ cDefine('EM_PROXIED_SBRK') }}}, bytes);
+ totalMemory = getTotalMemory()|0;
+ // Asking to increase dynamic top to a too high value? In pthreads builds we cannot
+ // enlarge memory, so this needs to fail.
+ if ((newDynamicTop|0) < 0 | (newDynamicTop|0) > (totalMemory|0)) {
+#if ABORTING_MALLOC
+ abortOnCannotGrowMemory()|0;
+#else
+ ___setErrNo({{{ cDefine('ENOMEM') }}});
+ return -1;
+#endif
+ }
+ Atomics_store(HEAP32, DYNAMICTOP_PTR>>2, newDynamicTop|0);
+#else // singlethreaded build: (-s USE_PTHREADS=0)
+ if ((newDynamicTop|0) < 0) {
+#if ABORTING_MALLOC
+ abortOnCannotGrowMemory()|0;
#endif
- // Implement a Linux-like 'memory area' for our 'process'.
- // Changes the size of the memory area by |bytes|; returns the
- // address of the previous top ('break') of the memory area
- // We control the "dynamic" memory - DYNAMIC_BASE to DYNAMICTOP
- var self = _sbrk;
- if (!self.called) {
- DYNAMICTOP = alignMemoryPage(DYNAMICTOP); // make sure we start out aligned
- self.called = true;
- assert(Runtime.dynamicAlloc);
- self.alloc = Runtime.dynamicAlloc;
- Runtime.dynamicAlloc = function() { abort('cannot dynamically allocate, sbrk now has control') };
+ ___setErrNo({{{ cDefine('ENOMEM') }}});
+ return -1;
}
- var ret = DYNAMICTOP;
- if (bytes != 0) {
- var success = self.alloc(bytes);
- if (!success) return -1 >>> 0; // sbrk failure code
+
+ oldDynamicTop = HEAP32[DYNAMICTOP_PTR>>2]|0;
+ HEAP32[DYNAMICTOP_PTR>>2] = newDynamicTop;
+ totalMemory = getTotalMemory()|0;
+ if ((newDynamicTop|0) > (totalMemory|0)) {
+ if ((enlargeMemory()|0) == 0) {
+ ___setErrNo({{{ cDefine('ENOMEM') }}});
+ HEAP32[DYNAMICTOP_PTR>>2] = oldDynamicTop;
+ return -1;
+ }
}
- return ret; // Previous break location.
+#endif
+ return 0;
},
system__deps: ['__setErrNo', '$ERRNO_CODES'],
diff --git a/src/library_bootstrap_structInfo.js b/src/library_bootstrap_structInfo.js
index a0808a7bebf5f..535265576f6bc 100644
--- a/src/library_bootstrap_structInfo.js
+++ b/src/library_bootstrap_structInfo.js
@@ -20,13 +20,13 @@ LibraryManager.library = {
// We control the "dynamic" memory - DYNAMIC_BASE to DYNAMICTOP
var self = _sbrk;
if (!self.called) {
- DYNAMICTOP = alignMemoryPage(DYNAMICTOP); // make sure we start out aligned
+ HEAP32[DYNAMICTOP_PTR>>2] = alignMemoryPage(HEAP32[DYNAMICTOP_PTR>>2]); // make sure we start out aligned
self.called = true;
assert(Runtime.dynamicAlloc);
self.alloc = Runtime.dynamicAlloc;
Runtime.dynamicAlloc = function() { abort('cannot dynamically allocate, sbrk now has control') };
}
- var ret = DYNAMICTOP;
+ var ret = HEAP32[DYNAMICTOP_PTR>>2];
if (bytes != 0) self.alloc(bytes);
return ret; // Previous break location.
},
diff --git a/src/library_gl.js b/src/library_gl.js
index 3bbb3af8a40d8..ca94fc916b509 100644
--- a/src/library_gl.js
+++ b/src/library_gl.js
@@ -2383,7 +2383,9 @@ var LibraryGL = {
}
#endif
var data = GLctx.getVertexAttrib(index, pname);
- if (typeof data == 'number' || typeof data == 'boolean') {
+ if (pname == 0x889F/*VERTEX_ATTRIB_ARRAY_BUFFER_BINDING*/) {
+ {{{ makeSetValue('params', '0', 'data["name"]', 'i32') }}};
+ } else if (typeof data == 'number' || typeof data == 'boolean') {
switch (type) {
case 'Integer': {{{ makeSetValue('params', '0', 'data', 'i32') }}}; break;
case 'Float': {{{ makeSetValue('params', '0', 'data', 'float') }}}; break;
diff --git a/src/library_glut.js b/src/library_glut.js
index 3a0f19486fb08..672af2c465144 100644
--- a/src/library_glut.js
+++ b/src/library_glut.js
@@ -337,12 +337,15 @@ var LibraryGLUT = {
var isTouchDevice = 'ontouchstart' in document.documentElement;
if (isTouchDevice) {
- // Historically handling of touch events was made in completely wrong way.
- // onMouseButtonDown, onMouseButtonUp, onMousemove handlers depends
- // on Browser.mouseX / Browser.mouseY fields. That fields
- // doesn`t update by touch events. BTW, in touch mode we interested only
- // in left-click (one finger), so we can use workaround and convert
- // all touch events in mouse events. See touchHandler.
+ // onMouseButtonDown, onMouseButtonUp and onMousemove handlers
+ // depend on Browser.mouseX / Browser.mouseY fields. Those fields
+ // don't get updated by touch events. So register a touchHandler
+ // function that translates the touch events to mouse events.
+
+ // GLUT doesn't support touch, mouse only, so from touch events we
+ // are only looking at single finger touches to emulate left click,
+ // so we can use workaround and convert all touch events in mouse
+ // events. See touchHandler.
window.addEventListener("touchmove", GLUT.touchHandler, true);
window.addEventListener("touchstart", GLUT.touchHandler, true);
window.addEventListener("touchend", GLUT.touchHandler, true);
diff --git a/src/library_pthread.js b/src/library_pthread.js
index 3400113b6550c..2548870dd4266 100644
--- a/src/library_pthread.js
+++ b/src/library_pthread.js
@@ -321,6 +321,10 @@ var LibraryPThread = {
url: currentScriptUrl,
buffer: HEAPU8.buffer,
tempDoublePtr: tempDoublePtr,
+ TOTAL_MEMORY: TOTAL_MEMORY,
+ STATICTOP: STATICTOP,
+ DYNAMIC_BASE: DYNAMIC_BASE,
+ DYNAMICTOP_PTR: DYNAMICTOP_PTR,
PthreadWorkerInit: PthreadWorkerInit
}, [HEAPU8.buffer]);
PThread.unusedWorkerPool.push(worker);
diff --git a/src/library_trace.js b/src/library_trace.js
index 689922191d3b8..fdf458f1c7441 100644
--- a/src/library_trace.js
+++ b/src/library_trace.js
@@ -262,7 +262,7 @@ var LibraryTracing = {
'stack_top': STACKTOP,
'stack_max': STACK_MAX,
'dynamic_base': DYNAMIC_BASE,
- 'dynamic_top': DYNAMICTOP,
+ 'dynamic_top': HEAP32[DYNAMICTOP_PTR>>2],
'total_memory': TOTAL_MEMORY
};
var now = EmscriptenTrace.now();
diff --git a/src/memoryprofiler.js b/src/memoryprofiler.js
index 118aa550c82af..dbd47cba85859 100644
--- a/src/memoryprofiler.js
+++ b/src/memoryprofiler.js
@@ -302,6 +302,7 @@ var emscriptenMemoryProfiler = {
html += '. STACK_MAX: ' + toHex(STACK_MAX, width) + '.';
html += '
STACK memory area used now (should be zero): ' + this.formatBytes(STACKTOP - STACK_BASE) + '.' + colorBar('#FFFF00') + ' STACK watermark highest seen usage (approximate lower-bound!): ' + this.formatBytes(this.stackTopWatermark - STACK_BASE);
+ var DYNAMICTOP = HEAP32[DYNAMICTOP_PTR>>2];
html += '
' + colorBar('#70FF70') + 'DYNAMIC memory area size: ' + this.formatBytes(DYNAMICTOP-DYNAMIC_BASE);
html += '. DYNAMIC_BASE: ' + toHex(DYNAMIC_BASE, width);
html += '. DYNAMICTOP: ' + toHex(DYNAMICTOP, width) + '.';
diff --git a/src/preamble.js b/src/preamble.js
index 77a8d8dc1350f..60ceb77623e0e 100644
--- a/src/preamble.js
+++ b/src/preamble.js
@@ -52,8 +52,13 @@ function SAFE_HEAP_STORE(dest, value, bytes, isFloat) {
#endif
if (dest <= 0) abort('segmentation fault storing ' + bytes + ' bytes to address ' + dest);
if (dest % bytes !== 0) abort('alignment error storing to address ' + dest + ', which was expected to be aligned to a multiple of ' + bytes);
- if (dest + bytes > Math.max(DYNAMICTOP, STATICTOP)) abort('segmentation fault, exceeded the top of the available heap when storing ' + bytes + ' bytes to address ' + dest + '. STATICTOP=' + STATICTOP + ', DYNAMICTOP=' + DYNAMICTOP);
- assert(DYNAMICTOP <= TOTAL_MEMORY);
+ if (staticSealed) {
+ if (dest + bytes > HEAP32[DYNAMICTOP_PTR>>2]) abort('segmentation fault, exceeded the top of the available dynamic heap when storing ' + bytes + ' bytes to address ' + dest + '. STATICTOP=' + STATICTOP + ', DYNAMICTOP=' + HEAP32[DYNAMICTOP_PTR>>2]);
+ assert(DYNAMICTOP_PTR);
+ assert(HEAP32[DYNAMICTOP_PTR>>2] <= TOTAL_MEMORY);
+ } else {
+ if (dest + bytes > STATICTOP) abort('segmentation fault, exceeded the top of the available static heap when storing ' + bytes + ' bytes to address ' + dest + '. STATICTOP=' + STATICTOP);
+ }
setValue(dest, value, getSafeHeapType(bytes, isFloat), 1);
}
function SAFE_HEAP_STORE_D(dest, value, bytes) {
@@ -63,8 +68,13 @@ function SAFE_HEAP_STORE_D(dest, value, bytes) {
function SAFE_HEAP_LOAD(dest, bytes, unsigned, isFloat) {
if (dest <= 0) abort('segmentation fault loading ' + bytes + ' bytes from address ' + dest);
if (dest % bytes !== 0) abort('alignment error loading from address ' + dest + ', which was expected to be aligned to a multiple of ' + bytes);
- if (dest + bytes > Math.max(DYNAMICTOP, STATICTOP)) abort('segmentation fault, exceeded the top of the available heap when loading ' + bytes + ' bytes from address ' + dest + '. STATICTOP=' + STATICTOP + ', DYNAMICTOP=' + DYNAMICTOP);
- assert(DYNAMICTOP <= TOTAL_MEMORY);
+ if (staticSealed) {
+ if (dest + bytes > HEAP32[DYNAMICTOP_PTR>>2]) abort('segmentation fault, exceeded the top of the available dynamic heap when loading ' + bytes + ' bytes from address ' + dest + '. STATICTOP=' + STATICTOP + ', DYNAMICTOP=' + HEAP32[DYNAMICTOP_PTR>>2]);
+ assert(DYNAMICTOP_PTR);
+ assert(HEAP32[DYNAMICTOP_PTR>>2] <= TOTAL_MEMORY);
+ } else {
+ if (dest + bytes > STATICTOP) abort('segmentation fault, exceeded the top of the available static heap when loading ' + bytes + ' bytes from address ' + dest + '. STATICTOP=' + STATICTOP);
+ }
var type = getSafeHeapType(bytes, isFloat);
var ret = getValue(dest, type, 1);
if (unsigned) ret = unSign(ret, parseInt(type.substr(1)), 1);
@@ -451,7 +461,7 @@ function allocate(slab, types, allocator, ptr) {
// Allocate memory during any stage of startup - static memory early on, dynamic memory later, malloc when ready
function getMemory(size) {
if (!staticSealed) return Runtime.staticAlloc(size);
- if ((typeof _sbrk !== 'undefined' && !_sbrk.called) || !runtimeInitialized) return Runtime.dynamicAlloc(size);
+ if (!runtimeInitialized) return Runtime.dynamicAlloc(size);
return _malloc(size);
}
{{{ maybeExport('getMemory') }}}
@@ -933,9 +943,18 @@ function updateGlobalBufferViews() {
Module['HEAPF64'] = HEAPF64 = new Float64Array(buffer);
}
-var STATIC_BASE = 0, STATICTOP = 0, staticSealed = false; // static area
-var STACK_BASE = 0, STACKTOP = 0, STACK_MAX = 0; // stack area
-var DYNAMIC_BASE = 0, DYNAMICTOP = 0; // dynamic area handled by sbrk
+var STATIC_BASE, STATICTOP, staticSealed; // static area
+var STACK_BASE, STACKTOP, STACK_MAX; // stack area
+var DYNAMIC_BASE, DYNAMICTOP_PTR; // dynamic area handled by sbrk
+
+#if USE_PTHREADS
+if (!ENVIRONMENT_IS_PTHREAD) { // Pthreads have already initialized these variables in src/pthread-main.js, where they were passed to the thread worker at startup time
+#endif
+ STATIC_BASE = STATICTOP = STACK_BASE = STACKTOP = STACK_MAX = DYNAMIC_BASE = DYNAMICTOP_PTR = 0;
+ staticSealed = false;
+#if USE_PTHREADS
+}
+#endif
#if USE_PTHREADS
if (ENVIRONMENT_IS_PTHREAD) {
@@ -969,11 +988,13 @@ function abortStackOverflow(allocSize) {
}
#endif
-#if ALLOW_MEMORY_GROWTH == 0
+#if ABORTING_MALLOC
function abortOnCannotGrowMemory() {
abort('Cannot enlarge memory arrays. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value ' + TOTAL_MEMORY + ', (2) compile with -s ALLOW_MEMORY_GROWTH=1 which adjusts the size at runtime but prevents some optimizations, (3) set Module.TOTAL_MEMORY to a higher value before the program runs, or if you want malloc to return NULL (0) instead of this abort, compile with -s ABORTING_MALLOC=0 ');
}
-#else // ALLOW_MEMORY_GROWTH
+#endif
+
+#if ALLOW_MEMORY_GROWTH
if (!Module['reallocBuffer']) Module['reallocBuffer'] = function(size) {
var ret;
try {
@@ -1007,7 +1028,7 @@ function enlargeMemory() {
#else
// TOTAL_MEMORY is the current size of the actual array, and DYNAMICTOP is the new top.
#if ASSERTIONS
- assert(DYNAMICTOP >= TOTAL_MEMORY);
+ assert(HEAP32[DYNAMICTOP_PTR>>2] > TOTAL_MEMORY); // This function should only ever be called after the ceiling of the dynamic heap has already been bumped to exceed the current total size of the asm.js heap.
assert(TOTAL_MEMORY > 4); // So the loop below will not be infinite
#endif
@@ -1020,14 +1041,14 @@ function enlargeMemory() {
var LIMIT = Math.pow(2, 31); // 2GB is a practical maximum, as we use signed ints as pointers
// and JS engines seem unhappy to give us 2GB arrays currently
- if (DYNAMICTOP >= LIMIT) return false;
+ if (HEAP32[DYNAMICTOP_PTR>>2] >= LIMIT) return false;
- while (TOTAL_MEMORY <= DYNAMICTOP) { // Simple heuristic.
+ while (TOTAL_MEMORY < HEAP32[DYNAMICTOP_PTR>>2]) { // Keep incrementing the heap size as long as it's less than what is requested.
if (TOTAL_MEMORY < LIMIT/2) {
- TOTAL_MEMORY = alignMemoryPage(2*TOTAL_MEMORY); // double until 1GB
+ TOTAL_MEMORY = alignMemoryPage(2*TOTAL_MEMORY); // // Simple heuristic: double until 1GB...
} else {
var last = TOTAL_MEMORY;
- TOTAL_MEMORY = alignMemoryPage((3*TOTAL_MEMORY + LIMIT)/4); // add smaller increments towards 2GB, which we cannot reach
+ TOTAL_MEMORY = alignMemoryPage((3*TOTAL_MEMORY + LIMIT)/4); // ..., but after that, add smaller increments towards 2GB, which we cannot reach
if (TOTAL_MEMORY <= last) return false;
}
}
@@ -1470,6 +1491,10 @@ function setF64(ptr, value) {
#endif // USE_PTHREADS
+function getTotalMemory() {
+ return TOTAL_MEMORY;
+}
+
// Endianness check (note: assumes compiler arch was little-endian)
#if SAFE_SPLIT_MEMORY == 0
#if USE_PTHREADS
@@ -1661,7 +1686,7 @@ function writeStringToMemory(string, buffer, dontAddNull) {
end = buffer + lengthBytesUTF8(string);
lastChar = HEAP8[end];
}
- stringToUTF8Array(string, HEAP8, buffer, Infinity);
+ stringToUTF8(string, buffer, Infinity);
if (dontAddNull) HEAP8[end] = lastChar; // Restore the value under the null character.
}
{{{ maybeExport('writeStringToMemory') }}}
diff --git a/src/pthread-main.js b/src/pthread-main.js
index b695b770df0b7..d3db0be12a186 100644
--- a/src/pthread-main.js
+++ b/src/pthread-main.js
@@ -2,22 +2,25 @@
// This is the entry point file that is loaded first by each Web Worker
// that executes pthreads on the Emscripten application.
-// All pthreads share the same Emscripten HEAP as SharedArrayBuffer
-// with the main execution thread.
-var buffer;
-
+// Thread-local:
var threadInfoStruct = 0; // Info area for this thread in Emscripten HEAP (shared). If zero, this worker is not currently hosting an executing pthread.
-
var selfThreadId = 0; // The ID of this thread. 0 if not hosting a pthread.
var parentThreadId = 0; // The ID of the parent pthread that launched this thread.
-
var tempDoublePtr = 0; // A temporary memory area for global float and double marshalling operations.
-// Each thread has its own allocated stack space.
+// Thread-local: Each thread has its own allocated stack space.
var STACK_BASE = 0;
var STACKTOP = 0;
var STACK_MAX = 0;
+// These are system-wide memory area parameters that are set at main runtime startup in main thread, and stay constant throughout the application.
+var buffer; // All pthreads share the same Emscripten HEAP as SharedArrayBuffer with the main execution thread.
+var DYNAMICTOP_PTR = 0;
+var TOTAL_MEMORY = 0;
+var STATICTOP = 0;
+var staticSealed = true; // When threads are being initialized, the static memory area has been already sealed a long time ago.
+var DYNAMIC_BASE = 0;
+
var ENVIRONMENT_IS_PTHREAD = true;
// Cannot use console.log or console.error in a web worker, since that would risk a browser deadlock! https://bugzilla.mozilla.org/show_bug.cgi?id=1049091
@@ -26,11 +29,11 @@ var Module = {};
function threadPrint() {
var text = Array.prototype.slice.call(arguments).join(' ');
- postMessage({cmd: 'print', text: text, threadId: selfThreadId});
+ console.log(text);
}
function threadPrintErr() {
var text = Array.prototype.slice.call(arguments).join(' ');
- postMessage({cmd: 'printErr', text: text, threadId: selfThreadId});
+ console.error(text);
}
function threadAlert() {
var text = Array.prototype.slice.call(arguments).join(' ');
@@ -38,19 +41,20 @@ function threadAlert() {
}
Module['print'] = threadPrint;
Module['printErr'] = threadPrintErr;
-
-// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1049091
-console = {
- log: threadPrint,
- error: threadPrintErr
-};
-
this.alert = threadAlert;
this.onmessage = function(e) {
if (e.data.cmd === 'load') { // Preload command that is called once per worker to parse and load the Emscripten code.
- buffer = e.data.buffer;
+ // Initialize the thread-local field(s):
tempDoublePtr = e.data.tempDoublePtr;
+
+ // Initialize the global "process"-wide fields:
+ buffer = e.data.buffer;
+ TOTAL_MEMORY = e.data.TOTAL_MEMORY;
+ STATICTOP = e.data.STATICTOP;
+ DYNAMIC_BASE = e.data.DYNAMIC_BASE;
+ DYNAMICTOP_PTR = e.data.DYNAMICTOP_PTR;
+
PthreadWorkerInit = e.data.PthreadWorkerInit;
importScripts(e.data.url);
FS.createStandardStreams();
diff --git a/src/runtime.js b/src/runtime.js
index cabeffa850745..d5c7cf6e011a4 100644
--- a/src/runtime.js
+++ b/src/runtime.js
@@ -59,15 +59,19 @@ var RuntimeGenerator = {
// allocation on the top of memory, adjusted dynamically by sbrk
dynamicAlloc: function(size) {
- if (ASSERTIONS) size = '(assert(DYNAMICTOP > 0),' + size + ')'; // dynamic area must be ready
-#if USE_PTHREADS
- if (typeof ENVIRONMENT_IS_PTHREAD !== 'undefined' && ENVIRONMENT_IS_PTHREAD) throw 'Runtime.dynamicAlloc is not available in pthreads!'; // This is because each worker has its own copy of DYNAMICTOP, of which main thread is authoritative.
-#endif
- var ret = RuntimeGenerator.alloc(size, 'DYNAMIC');
- if (SAFE_HEAP) ret += '; if (asm) { Runtime.setDynamicTop(DYNAMICTOP); }';
- ret += '; if (DYNAMICTOP >= TOTAL_MEMORY) { var success = enlargeMemory(); if (!success) { DYNAMICTOP = ret; ';
- if (SAFE_HEAP) ret += 'if (asm) { Runtime.setDynamicTop(DYNAMICTOP); }';
- ret += ' return 0; } }'
+ var ret = '';
+ if (ASSERTIONS) ret += 'assert(DYNAMICTOP_PTR);'; // dynamic area must be ready
+ ret += 'var ret = HEAP32[DYNAMICTOP_PTR>>2];'
+ + 'var end = (((ret + size + 15)|0) & -16);'
+ + 'HEAP32[DYNAMICTOP_PTR>>2] = end;'
+ + 'if (end >= TOTAL_MEMORY) {'
+ + 'var success = enlargeMemory();'
+ + 'if (!success) {'
+ + 'HEAP32[DYNAMICTOP_PTR>>2] = ret;'
+ + 'return 0;'
+ + '}'
+ + '}'
+ + 'return ret;';
return ret;
},
@@ -100,8 +104,12 @@ var RuntimeGenerator = {
}
};
-function unInline(name_, params) {
- var src = '(function(' + params + ') { var ret = ' + RuntimeGenerator[name_].apply(null, params) + '; return ret; })';
+function unInline(name_, params, isExpression) {
+ if (isExpression) {
+ var src = '(function(' + params + ') { var ret = ' + RuntimeGenerator[name_].apply(null, params) + '; return ret; })';
+ } else {
+ var src = '(function(' + params + ') { ' + RuntimeGenerator[name_].apply(null, params) + '})';
+ }
var ret = eval(src);
return ret;
}
@@ -429,11 +437,11 @@ var Runtime = {
#endif
};
-Runtime.stackAlloc = unInline('stackAlloc', ['size']);
-Runtime.staticAlloc = unInline('staticAlloc', ['size']);
-Runtime.dynamicAlloc = unInline('dynamicAlloc', ['size']);
-Runtime.alignMemory = unInline('alignMemory', ['size', 'quantum']);
-Runtime.makeBigInt = unInline('makeBigInt', ['low', 'high', 'unsigned']);
+Runtime.stackAlloc = unInline('stackAlloc', ['size'], true);
+Runtime.staticAlloc = unInline('staticAlloc', ['size'], true);
+Runtime.dynamicAlloc = unInline('dynamicAlloc', ['size'], false);
+Runtime.alignMemory = unInline('alignMemory', ['size', 'quantum'], true);
+Runtime.makeBigInt = unInline('makeBigInt', ['low', 'high', 'unsigned'], true);
if (MAIN_MODULE || SIDE_MODULE) {
Runtime.tempRet0 = 0;
diff --git a/system/include/emscripten/threading.h b/system/include/emscripten/threading.h
index cc01d2fcab6fb..560c367428e6e 100644
--- a/system/include/emscripten/threading.h
+++ b/system/include/emscripten/threading.h
@@ -175,7 +175,6 @@ struct thread_profiler_block
#define EM_PROXIED_FPATHCONF 46
#define EM_PROXIED_CONFSTR 68
#define EM_PROXIED_SYSCONF 72
-#define EM_PROXIED_SBRK 73
#define EM_PROXIED_ATEXIT 110
#define EM_PROXIED_GETENV 111
#define EM_PROXIED_CLEARENV 112
diff --git a/system/lib/pthread/library_pthread.c b/system/lib/pthread/library_pthread.c
index c630fcdc46682..43465baf615f9 100644
--- a/system/lib/pthread/library_pthread.c
+++ b/system/lib/pthread/library_pthread.c
@@ -282,7 +282,6 @@ static void _do_call(em_queued_call *q)
case EM_PROXIED_FPATHCONF: q->returnValue.i = fpathconf(q->args[0].i, q->args[1].i); break;
case EM_PROXIED_CONFSTR: q->returnValue.i = confstr(q->args[0].i, q->args[1].cp, q->args[2].i); break;
case EM_PROXIED_SYSCONF: q->returnValue.i = sysconf(q->args[0].i); break;
- case EM_PROXIED_SBRK: q->returnValue.vp = sbrk(q->args[0].i); break;
case EM_PROXIED_ATEXIT: q->returnValue.i = atexit(q->args[0].vp); break;
case EM_PROXIED_GETENV: q->returnValue.cp = getenv(q->args[0].cp); break;
case EM_PROXIED_CLEARENV: q->returnValue.i = clearenv(); break;
diff --git a/system/lib/split_malloc.cpp b/system/lib/split_malloc.cpp
index 8a697da69fbbb..f4d60f69a5cba 100644
--- a/system/lib/split_malloc.cpp
+++ b/system/lib/split_malloc.cpp
@@ -71,7 +71,7 @@ struct Space {
start = split_memory*index;
} else {
// small area in existing chunk 0
- start = EM_ASM_INT_V({ return (DYNAMICTOP+3)&-4; });
+ start = EM_ASM_INT_V({ return (HEAP32[DYNAMICTOP_PTR>>2]+3)&-4; });
assert(start < split_memory);
}
int size = (split_memory*(index+1)) - start;
diff --git a/tests/gauge_available_memory.cpp b/tests/gauge_available_memory.cpp
new file mode 100644
index 0000000000000..2edc126321310
--- /dev/null
+++ b/tests/gauge_available_memory.cpp
@@ -0,0 +1,27 @@
+#include
+#include
+#include
+
+// Without noinline, Clang optimizes this whole application to a while(1) {} infinite loop.
+void * __attribute__((noinline)) leak_alloc(uint64_t i)
+{
+ return malloc(i);
+}
+
+int main()
+{
+ // Keep allocating in ~powers of two until we fail, then print out how much we got.
+ uint64_t availableMemory = 0;
+ uint64_t i = 0x80000000ULL;
+ while(i > 0)
+ {
+ void *ptr = leak_alloc(i);
+ if (ptr) availableMemory += i;
+ else i >>= 1;
+ }
+ printf("Total memory available: %llu\n", availableMemory);
+#ifdef REPORT_RESULT
+ int result = (availableMemory > 10*1024*1024);
+ REPORT_RESULT();
+#endif
+}
diff --git a/tests/pthread/test_pthread_sbrk.cpp b/tests/pthread/test_pthread_sbrk.cpp
new file mode 100644
index 0000000000000..b39948a163930
--- /dev/null
+++ b/tests/pthread/test_pthread_sbrk.cpp
@@ -0,0 +1,112 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define NUM_THREADS 8
+#define NUM_ALLOCATIONS 10240
+#if ABORTING_MALLOC
+#define ALLOCATION_SIZE 1280 // Malloc aborts, so allocate a bit less of memory so all fits
+#else
+#define ALLOCATION_SIZE 2560 // Malloc doesn't abort, allocate a bit more memory to test graceful allocation failures
+#endif
+
+// Use barriers to make each thread synchronize their execution points, to maximize the possibility of seeing race conditions
+// if those might occur.
+static pthread_barrier_t barrierWaitToAlloc;
+static pthread_barrier_t barrierWaitToVerify;
+static pthread_barrier_t barrierWaitToFree;
+
+static void *thread_start(void *arg)
+{
+ int id = (int)(arg)+1;
+ int return_code = 0;
+
+ uint8_t *allocated_buffers[NUM_ALLOCATIONS] = {};
+
+ int some_allocations_failed = 0;
+
+ pthread_barrier_wait(&barrierWaitToAlloc); // Halt until all threads reach here, then proceed synchronously.
+ for(int i = 0; i < NUM_ALLOCATIONS; ++i)
+ {
+ allocated_buffers[i] = (uint8_t*)malloc(ALLOCATION_SIZE);
+ if (allocated_buffers[i])
+ memset(allocated_buffers[i], id, ALLOCATION_SIZE);
+ else
+ some_allocations_failed = 1;
+ }
+
+ pthread_barrier_wait(&barrierWaitToVerify); // Halt until all threads reach here, then proceed synchronously.
+ int reported_once = 0;
+ for(int i = 0; i < NUM_ALLOCATIONS; ++i)
+ {
+ if (!allocated_buffers[i]) continue;
+ for(int j = 0; j < ALLOCATION_SIZE; ++j)
+ if (allocated_buffers[i][j] != id)
+ {
+ ++return_code; // Failed! (but run to completion so that the barriers will all properly proceed without hanging)
+ if (!reported_once) {
+ EM_ASM_INT( { console.error('Memory corrupted! mem[i]: ' + $0 + ' != ' + $1 + ', i: ' + $2 + ', j: ' + $3); }, allocated_buffers[i][j], id, i, j);
+ reported_once = 1; // Avoid print flood that makes debugging hard.
+ }
+ }
+ }
+
+ pthread_barrier_wait(&barrierWaitToFree); // Halt until all threads reach here, then proceed synchronously.
+ for(int i = 0; i < NUM_ALLOCATIONS; ++i)
+ free(allocated_buffers[i]);
+
+#if ABORTING_MALLOC
+ if (some_allocations_failed)
+ return_code = 12345678; // We expect allocations not to fail (if they did, shouldn't reach here, but we should have aborted)
+#else
+ if (!some_allocations_failed)
+ return_code = 12345678; // We expect to be allocating so much memory that some of the allocations fail.
+#endif
+ pthread_exit((void*)return_code);
+}
+
+int main()
+{
+ int result = 0;
+ if (!emscripten_has_threading_support()) {
+#ifdef REPORT_RESULT
+ REPORT_RESULT();
+#endif
+ printf("Skipped: threading support is not available!\n");
+ return 0;
+ }
+
+ int ret = pthread_barrier_init(&barrierWaitToAlloc, NULL, NUM_THREADS);
+ assert(ret == 0);
+ ret = pthread_barrier_init(&barrierWaitToVerify, NULL, NUM_THREADS);
+ assert(ret == 0);
+ ret = pthread_barrier_init(&barrierWaitToFree, NULL, NUM_THREADS);
+ assert(ret == 0);
+
+ pthread_t thr[8/*NUM_THREADS*/];
+ for(int i = 0; i < NUM_THREADS; ++i)
+ {
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setstacksize(&attr, NUM_ALLOCATIONS*80);
+ ret = pthread_create(&thr[i], &attr, thread_start, (void*)(i));
+ assert(ret == 0);
+ }
+
+ for(int i = 0; i < NUM_THREADS; ++i) {
+ int res = 0;
+ ret = pthread_join(thr[i], (void**)&res);
+ assert(ret == 0);
+ result += res;
+ if (res) printf("Thread %d failed with return code %d.\n", i, res);
+ }
+ printf("Test finished with result %d\n", result);
+
+#ifdef REPORT_RESULT
+ REPORT_RESULT();
+#endif
+}
diff --git a/tests/sbrk_brk.cpp b/tests/sbrk_brk.cpp
new file mode 100644
index 0000000000000..bca7690b616d6
--- /dev/null
+++ b/tests/sbrk_brk.cpp
@@ -0,0 +1,96 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+// A custom tiny malloc & free impl
+uintptr_t arena_end = 0;
+
+struct mem_node
+{
+ uint32_t size;
+ mem_node *next;
+};
+
+// List of reclaimed free memory nodes
+mem_node free_memory_root = {};
+
+void *mymalloc(size_t size)
+{
+ // Static init at program startup:
+ if (!arena_end)
+ {
+ arena_end = (uintptr_t)sbrk(0);
+ assert(arena_end != (uintptr_t)-1);
+ }
+
+ // Find if there is an existing node we can reuse.
+ mem_node *prev = &free_memory_root;
+ mem_node *n = prev->next;
+ while(n)
+ {
+ if (n->size >= size)
+ {
+ prev->next = n->next; // Splice this node off from the free list.
+ return (void*)((uintptr_t)n + sizeof(mem_node));
+ }
+ prev = n;
+ n = n->next;
+ }
+
+ // If not, allocate new node from empty area
+ size_t allocated_size = sizeof(mem_node) + size;
+
+#if TEST_BRK // test brk()
+ uintptr_t new_brk = arena_end + allocated_size;
+ int failed = brk((void*)new_brk);
+ if (failed) return 0;
+
+ mem_node *node = (mem_node*)arena_end;
+ arena_end = (uintptr_t)sbrk(0);
+ assert(arena_end == new_brk);
+#else // test sbrk()
+ mem_node *node = (mem_node*)sbrk(allocated_size);
+ if ((uintptr_t)node == (uintptr_t)-1)
+ return 0;
+#endif
+ node->size = size;
+ return (void*)((uintptr_t)node + sizeof(mem_node));
+}
+
+void myfree(void *ptr)
+{
+ mem_node *freed_node = (mem_node*)((uintptr_t)ptr - sizeof(mem_node));
+ freed_node->next = free_memory_root.next;
+ free_memory_root.next = freed_node;
+}
+
+int main()
+{
+#define N 3000 // Arbitrary amount that fits within the default 16MB heap
+
+ uint8_t *data[N];
+ for(int i = 0; i < N; ++i)
+ {
+ int count = i&~15U;
+ uint8_t *memory = (uint8_t*)mymalloc(count);
+ assert(memory);
+ uint8_t *memory2 = (uint8_t*)mymalloc(count);
+ assert(memory2);
+
+ myfree(memory);
+ data[i] = memory2;
+ memset(memory2, (uint8_t)i, count);
+ }
+ uintptr_t dynamicTop = (uintptr_t)sbrk(0);
+ for(int i = 0; i < N; ++i)
+ {
+ int count = i&~15U;
+ assert((uintptr_t)data[i] + count <= dynamicTop);
+ for(int j = 0; j < count; ++j)
+ assert(data[i][j] == (uint8_t)i);
+ }
+ printf("OK. brk at end: %u. \n", (uintptr_t)sbrk(0));
+}
diff --git a/tests/test_browser.py b/tests/test_browser.py
index c372341d69c33..76d84bc9a4637 100644
--- a/tests/test_browser.py
+++ b/tests/test_browser.py
@@ -3018,6 +3018,20 @@ def test_pthread_custom_pthread_main_url(self):
def test_pthread_proxying_in_futex_wait(self):
self.btest(path_from_root('tests', 'pthread', 'test_pthread_proxying_in_futex_wait.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=2', '-s', 'PTHREAD_POOL_SIZE=1', '--separate-asm'], timeout=30)
+ # Test that sbrk() operates properly in multithreaded conditions
+ def test_pthread_sbrk(self):
+ for aborting_malloc in [0, 1]:
+ print 'aborting malloc=' + str(aborting_malloc)
+ # With aborting malloc = 1, test allocating memory in threads
+ # With aborting malloc = 0, allocate so much memory in threads that some of the allocations fail.
+ self.btest(path_from_root('tests', 'pthread', 'test_pthread_sbrk.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8', '--separate-asm', '-s', 'ABORTING_MALLOC=' + str(aborting_malloc), '-DABORTING_MALLOC=' + str(aborting_malloc), '-s', 'TOTAL_MEMORY=134217728'], timeout=30)
+
+ # Test that -s ABORTING_MALLOC=0 works in both pthreads and non-pthreads builds. (sbrk fails gracefully)
+ def test_pthread_gauge_available_memory(self):
+ for opts in [[], ['-O2']]:
+ for args in [[], ['-s', 'USE_PTHREADS=1']]:
+ self.btest(path_from_root('tests', 'gauge_available_memory.cpp'), expected='1', args=['-s', 'ABORTING_MALLOC=0'] + args + opts, timeout=30)
+
# test atomicrmw i64
def test_atomicrmw_i64(self):
Popen([PYTHON, EMCC, path_from_root('tests', 'atomicrmw_i64.ll'), '-s', 'USE_PTHREADS=1', '-s', 'IN_TEST_HARNESS=1', '-o', 'test.html']).communicate()
diff --git a/tests/test_core.py b/tests/test_core.py
index 77c5da3005164..aed985ffb9b9e 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -1434,7 +1434,7 @@ def test_exceptions_white_list(self):
Settings.DISABLE_EXCEPTION_CATCHING = 2
# Wasm does not add an underscore to function names. For wasm, the
# mismatches are fixed in fixImports() function in JS glue code.
- if not Settings.BINARYEN:
+ if not self.is_wasm_backend():
Settings.EXCEPTION_CATCHING_WHITELIST = ["__Z12somefunctionv"]
else:
Settings.EXCEPTION_CATCHING_WHITELIST = ["_Z12somefunctionv"]
@@ -1477,7 +1477,7 @@ def test_exceptions_white_list_2(self):
Settings.DISABLE_EXCEPTION_CATCHING = 2
# Wasm does not add an underscore to function names. For wasm, the
# mismatches are fixed in fixImports() function in JS glue code.
- if not Settings.BINARYEN:
+ if not self.is_wasm_backend():
Settings.EXCEPTION_CATCHING_WHITELIST = ["_main"]
else:
Settings.EXCEPTION_CATCHING_WHITELIST = ["main"]
@@ -8160,7 +8160,7 @@ def test_memprof_requirements(self):
typeof STACK_MAX === 'number' &&
typeof STACKTOP === 'number' &&
typeof DYNAMIC_BASE === 'number' &&
- typeof DYNAMICTOP === 'number') {
+ typeof DYNAMICTOP_PTR === 'number') {
Module.print('able to run memprof');
} else {
Module.print('missing the required variables to run memprof');
@@ -8204,6 +8204,13 @@ def test_binaryen(self):
self.emcc_args += ['-s', 'BINARYEN=1', '-s', 'BINARYEN_METHOD="interpret-binary"']
self.do_run(open(path_from_root('tests', 'hello_world.c')).read(), 'hello, world!')
+ def test_sbrk(self):
+ self.do_run(open(path_from_root('tests', 'sbrk_brk.cpp')).read(), 'OK.')
+
+ def test_brk(self):
+ self.emcc_args += ['-DTEST_BRK=1']
+ self.do_run(open(path_from_root('tests', 'sbrk_brk.cpp')).read(), 'OK.')
+
# Generate tests for everything
def make_run(fullname, name=-1, compiler=-1, embetter=0, quantum_size=0,
typed_arrays=0, emcc_args=None, env=None):
diff --git a/tests/test_other.py b/tests/test_other.py
index 70756dcffc1e4..3102c13f4431e 100644
--- a/tests/test_other.py
+++ b/tests/test_other.py
@@ -754,6 +754,9 @@ def build(path, args):
assert not os.path.exists('a.out') and not os.path.exists('a.exe'), 'Must not leave unneeded linker stubs'
def test_outline(self):
+ if WINDOWS and not Building.which('mingw32-make'):
+ return self.skip('Skipping other.test_outline: This test requires "mingw32-make" tool in PATH on Windows to drive a Makefile build of zlib')
+
def test(name, src, libs, expected, expected_ranges, args=[], suffix='cpp'):
print name
@@ -5135,7 +5138,7 @@ def test_massive_alloc(self):
def test_failing_alloc(self):
for pre_fail, post_fail, opts in [
('', '', []),
- ('EM_ASM( Module.temp = DYNAMICTOP );', 'EM_ASM( assert(Module.temp === DYNAMICTOP, "must not adjust DYNAMICTOP when an alloc fails!") );', []),
+ ('EM_ASM( Module.temp = HEAP32[DYNAMICTOP_PTR>>2] );', 'EM_ASM( assert(Module.temp === HEAP32[DYNAMICTOP_PTR>>2], "must not adjust DYNAMICTOP when an alloc fails!") );', []),
('', '', ['-s', 'SPLIT_MEMORY=' + str(16*1024*1024)]),
]:
for growth in [0, 1]:
@@ -6518,12 +6521,20 @@ def test_disable_inlining(self):
try_delete('test.bc')
def test_output_eol(self):
- for params in [[], ['--separate-asm']]:
- for eol in ['windows', 'linux']:
- Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-o', 'a.html', '--output_eol', eol] + params).communicate()
- for f in ['a.html', 'a.js', 'a.asm.js']:
- if os.path.isfile(f):
- print str(params) + ' ' + eol + ' ' + f
+ for params in [[], ['--separate-asm'], ['--proxy-to-worker'], ['--proxy-to-worker', '--separate-asm']]:
+ for output_suffix in ['html', 'js']:
+ for eol in ['windows', 'linux']:
+ files = ['a.js']
+ if '--separate-asm' in params: files += ['a.asm.js']
+ if output_suffix == 'html': files += ['a.html']
+ cmd = [PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-o', 'a.' + output_suffix, '--output_eol', eol] + params
+ Popen(cmd).communicate()
+ for f in files:
+ print str(cmd) + ' ' + str(params) + ' ' + eol + ' ' + f
+ assert os.path.isfile(f)
ret = tools.line_endings.check_line_endings(f, expect_only_specific_line_endings='\n' if eol == 'linux' else '\r\n')
assert ret == 0
+ for f in files:
+ try_delete(f)
+
diff --git a/tests/webgl_create_context.cpp b/tests/webgl_create_context.cpp
index 147202b961cac..e63f202f66500 100644
--- a/tests/webgl_create_context.cpp
+++ b/tests/webgl_create_context.cpp
@@ -136,6 +136,27 @@ int main()
assert(!!numSamples == !!antialias);
printf("\n");
+ // Test bug https://github.com/kripken/emscripten/issues/1330:
+ unsigned vb;
+ glGenBuffers(1, &vb);
+ glBindBuffer(GL_ARRAY_BUFFER, vb);
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
+
+ unsigned vb2;
+ glGenBuffers(1, &vb2);
+ glBindBuffer(GL_ARRAY_BUFFER, vb2);
+ glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);
+
+ int vb3;
+ glGetVertexAttribiv(0, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &vb3);
+ if (vb != vb3) printf("Index 0: Generated VB: %d, read back VB: %d\n", vb, vb3);
+ assert(vb == vb3);
+
+ int vb4;
+ glGetVertexAttribiv(1, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &vb4);
+ if (vb2 != vb4) printf("Index 1: Generated VB: %d, read back VB: %d\n", vb2, vb4);
+ assert(vb2 == vb4);
+
// Test that deleting the context works.
res = emscripten_webgl_destroy_context(context);
assert(res == 0);
diff --git a/tools/ctor_evaller.py b/tools/ctor_evaller.py
index b4eb1916deee7..d2fb1ba72f73a 100644
--- a/tools/ctor_evaller.py
+++ b/tools/ctor_evaller.py
@@ -61,6 +61,8 @@ def add_func(asm, func):
asm = get_asm(js)
assert len(asm) > 0
asm = asm.replace('use asm', 'not asm') # don't try to validate this
+ # Substitute sbrk with a failing stub: the dynamic heap memory area shouldn't get increased during static ctor initialization.
+ asm = asm.replace('function _sbrk(', 'function _sbrk(increment) { throw "no sbrk when evalling ctors!"; } function KILLED_sbrk(', 1)
# find all global vars, and provide only safe ones. Also add dumping for those.
pre_funcs_start = asm.find(';') + 1
pre_funcs_end = asm.find('function ', pre_funcs_start)
@@ -75,13 +77,13 @@ def add_func(asm, func):
for bit in bits:
name, value = map(lambda x: x.strip(), bit.split('='))
if value in ['0', '+0', '0.0'] or name in [
- 'STACKTOP', 'STACK_MAX', 'DYNAMICTOP',
+ 'STACKTOP', 'STACK_MAX', 'DYNAMICTOP_PTR',
'HEAP8', 'HEAP16', 'HEAP32',
'HEAPU8', 'HEAPU16', 'HEAPU32',
'HEAPF32', 'HEAPF64',
'Int8View', 'Int16View', 'Int32View', 'Uint8View', 'Uint16View', 'Uint32View', 'Float32View', 'Float64View',
'nan', 'inf',
- '_emscripten_memcpy_big', '_sbrk', '___dso_handle',
+ '_emscripten_memcpy_big', '___dso_handle',
'_atexit', '___cxa_atexit',
] or name.startswith('Math_'):
if 'new ' not in value:
@@ -108,6 +110,7 @@ def add_func(asm, func):
var buffer = new ArrayBuffer(totalMemory);
var heap = new Uint8Array(buffer);
+var heapi32 = new Int32Array(buffer);
var memInit = %s;
@@ -125,7 +128,8 @@ def add_func(asm, func):
var stackMax = stackTop + totalStack;
if (stackMax >= totalMemory) throw 'not enough room for stack';
-var dynamicTop = stackMax;
+var dynamicTopPtr = stackMax;
+heapi32[dynamicTopPtr >> 2] = stackMax;
if (!Math.imul) {
Math.imul = Math.imul || function(a, b) {
@@ -162,7 +166,7 @@ def add_func(asm, func):
var libraryArg = {
STACKTOP: stackTop,
STACK_MAX: stackMax,
- DYNAMICTOP: dynamicTop,
+ DYNAMICTOP_PTR: dynamicTopPtr,
___dso_handle: 0, // used by atexit, value doesn't matter
_emscripten_memcpy_big: function(dest, src, num) {
heap.set(heap.subarray(src, src+num), dest);
@@ -198,6 +202,10 @@ def add_func(asm, func):
console.warn('globals modified');
break;
}
+ if (heapi32[dynamicTopPtr >> 2] !== stackMax) {
+ console.warn('dynamic allocation was performend');
+ break;
+ }
// this one was ok.
numSuccessful = i + 1;
@@ -238,7 +246,7 @@ def read_and_delete(filename):
out_result = read_and_delete(out_file)
err_result = read_and_delete(err_file)
if proc.returncode != 0:
- shared.logging.debug('unexpected error while trying to eval ctors:\n' + out_result)
+ shared.logging.debug('unexpected error while trying to eval ctors:\n' + out_result + '\n' + err_result)
return (0, 0, 0, 0)
# out contains the new mem init and other info
diff --git a/tools/shared.py b/tools/shared.py
index 89592974b450b..33f7e92b3d91b 100644
--- a/tools/shared.py
+++ b/tools/shared.py
@@ -652,32 +652,45 @@ def get_clang_native_env():
return env
if 'VSINSTALLDIR' in env:
- visual_studio_2013_path = env['VSINSTALLDIR']
+ visual_studio_path = env['VSINSTALLDIR']
elif 'VS120COMNTOOLS' in env:
- visual_studio_2013_path = os.path.normpath(os.path.join(env['VS120COMNTOOLS'], '../..'))
+ visual_studio_path = os.path.normpath(os.path.join(env['VS120COMNTOOLS'], '../..'))
+ elif 'VS140COMNTOOLS' in env:
+ visual_studio_path = os.path.normpath(os.path.join(env['VS140COMNTOOLS'], '../..'))
elif 'ProgramFiles(x86)' in env:
- visual_studio_2013_path = os.path.normpath(os.path.join(env['ProgramFiles(x86)'], 'Microsoft Visual Studio 12.0'))
+ visual_studio_path = os.path.normpath(os.path.join(env['ProgramFiles(x86)'], 'Microsoft Visual Studio 12.0'))
elif 'ProgramFiles' in env:
- visual_studio_2013_path = os.path.normpath(os.path.join(env['ProgramFiles'], 'Microsoft Visual Studio 12.0'))
+ visual_studio_path = os.path.normpath(os.path.join(env['ProgramFiles'], 'Microsoft Visual Studio 12.0'))
else:
- visual_studio_2013_path = 'C:\\Program Files (x86)\\Microsoft Visual Studio 12.0'
- if not os.path.isdir(visual_studio_2013_path):
- raise Exception('Visual Studio 2013 was not found in "' + visual_studio_2013_path + '"! Run in Visual Studio command prompt to avoid the need to autoguess this location (or set VSINSTALLDIR env var).')
+ visual_studio_path = 'C:\\Program Files (x86)\\Microsoft Visual Studio 12.0'
+ if not os.path.isdir(visual_studio_path):
+ raise Exception('Visual Studio 2013 was not found in "' + visual_studio_path + '"! Run in Visual Studio command prompt to avoid the need to autoguess this location (or set VSINSTALLDIR env var).')
+
+ env['INCLUDE'] = os.path.join(visual_studio_path, 'VC\\INCLUDE')
+
+ if 'ProgramFiles(x86)' in env: prog_files_x86 = env['ProgramFiles(x86)']
+ elif 'ProgramFiles' in env: prog_files_x86 = env['ProgramFiles']
+ elif os.path.isdir('C:\\Program Files (x86)'): prog_files_x86 = 'C:\\Program Files (x86)'
+ elif os.path.isdir('C:\\Program Files'): prog_files_x86 = 'C:\\Program Files'
+ else:
+ raise Exception('Unable to detect Program files directory for native Visual Studio build!')
if 'WindowsSdkDir' in env:
windows_sdk_dir = env['WindowsSdkDir']
- elif 'ProgramFiles(x86)' in env:
- windows_sdk_dir = os.path.normpath(os.path.join(env['ProgramFiles(x86)'], 'Windows Kits\\8.1'))
- elif 'ProgramFiles' in env:
- windows_sdk_dir = os.path.normpath(os.path.join(env['ProgramFiles'], 'Windows Kits\\8.1'))
+ elif os.path.isdir(os.path.join(prog_files_x86, 'Windows Kits', '10')):
+ windows_sdk_dir = os.path.join(prog_files_x86, 'Windows Kits', '10')
+ include_dir = os.path.join(windows_sdk_dir, 'Include')
+ include_dir = [os.path.join(include_dir,x) for x in os.listdir(include_dir) if os.path.isdir(os.path.join(include_dir, x))][0] + '\\ucrt'
+ env['INCLUDE'] = env['INCLUDE'] + ';' + include_dir
+ elif os.path.isdir(os.path.join(prog_files_x86, 'Windows Kits', '8.1')):
+ windows_sdk_dir = os.path.join(prog_files_x86, 'Windows Kits', '8.1')
else:
windows_sdk_dir = 'C:\\Program Files (x86)\\Windows Kits\\8.1'
if not os.path.isdir(windows_sdk_dir):
raise Exception('Windows SDK was not found in "' + windows_sdk_dir + '"! Run in Visual Studio command prompt to avoid the need to autoguess this location (or set WindowsSdkDir env var).')
- env['INCLUDE'] = os.path.join(visual_studio_2013_path, 'VC\\INCLUDE')
- env['LIB'] = os.path.join(visual_studio_2013_path, 'VC\\LIB\\amd64') + ';' + os.path.join(windows_sdk_dir, 'lib\\winv6.3\\um\\x64')
- env['PATH'] = env['PATH'] + ';' + os.path.join(visual_studio_2013_path, 'VC\\BIN')
+ env['LIB'] = os.path.join(visual_studio_path, 'VC\\LIB\\amd64') + ';' + os.path.join(windows_sdk_dir, 'lib\\winv6.3\\um\\x64')
+ env['PATH'] = env['PATH'] + ';' + os.path.join(visual_studio_path, 'VC\\BIN')
# Current configuration above is all Visual Studio -specific, so on non-Windowses, no action needed.