diff --git a/ChangeLog.md b/ChangeLog.md index fe16e915f870e..f8e4f84f8a27d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -23,6 +23,12 @@ Current Trunk - dlopen, in conformace with the spec, now checks that one of either RTDL_LAZY or RTDL_NOW flags ar set. Previously, it was possible set nether of these without generating an error. +- Stack state is no longer stored in JavaScript. The following variables have + been replaced with native functions in ``: + - STACK_BASE + - STACK_MAX + - STACKTOP + - TOTAL_STACK 2.0.8: 10/24/2020 ----------------- diff --git a/emcc.py b/emcc.py index 59f0739300fc5..aedeccda8a89f 100755 --- a/emcc.py +++ b/emcc.py @@ -1351,13 +1351,19 @@ def filter_out_duplicate_dynamic_libs(inputs): shared.Settings.EXPORTED_FUNCTIONS += ['_sbrk'] if shared.Settings.MEMORYPROFILER: - shared.Settings.EXPORTED_FUNCTIONS += ['___heap_base'] + shared.Settings.EXPORTED_FUNCTIONS += ['___heap_base', + '_emscripten_stack_get_base', + '_emscripten_stack_get_end', + '_emscripten_stack_get_current'] if shared.Settings.ASYNCIFY: # See: https://github.com/emscripten-core/emscripten/issues/12065 # See: https://github.com/emscripten-core/emscripten/issues/12066 shared.Settings.USE_LEGACY_DYNCALLS = 1 shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$getDynCaller'] + shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_stack_get_base', + '_emscripten_stack_get_end', + '_emscripten_stack_set_limits'] # Reconfigure the cache now that settings have been applied. Some settings # such as LTO and SIDE_MODULE/MAIN_MODULE effect which cache directory we use. @@ -1398,6 +1404,13 @@ def filter_out_duplicate_dynamic_libs(inputs): if shared.Settings.STACK_OVERFLOW_CHECK: shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$abortStackOverflow'] shared.Settings.EXPORTED_RUNTIME_METHODS += ['writeStackCookie', 'checkStackCookie'] + shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_stack_get_end', '_emscripten_stack_get_free'] + if shared.Settings.RELOCATABLE: + shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_stack_set_limits'] + else: + shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_stack_init'] + if shared.Settings.STACK_OVERFLOW_CHECK == 2: + shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_stack_get_base'] if shared.Settings.MODULARIZE: assert not options.proxy_to_worker, '-s MODULARIZE=1 is not compatible with --proxy-to-worker (if you want to run in a worker with -s MODULARIZE=1, you likely want to do the worker side setup manually)' @@ -1539,7 +1552,7 @@ def filter_out_duplicate_dynamic_libs(inputs): if shared.Settings.SAFE_HEAP: # SAFE_HEAP check includes calling emscripten_get_sbrk_ptr() from wasm - shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_get_sbrk_ptr'] + shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_get_sbrk_ptr', '_emscripten_stack_get_base'] shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$unSign'] if not shared.Settings.DECLARE_ASM_MODULE_EXPORTS: @@ -1568,7 +1581,8 @@ def filter_out_duplicate_dynamic_libs(inputs): shared.Settings.EXPORTED_FUNCTIONS += [ '_emscripten_get_global_libc', '___pthread_tsd_run_dtors', 'registerPthreadPtr', '_pthread_self', - '___emscripten_pthread_data_constructor', '_emscripten_futex_wake'] + '___emscripten_pthread_data_constructor', '_emscripten_futex_wake', + '_emscripten_stack_set_limits'] # set location of worker.js shared.Settings.PTHREAD_WORKER_FILE = unsuffixed(os.path.basename(target)) + '.worker.js' @@ -1641,10 +1655,6 @@ def include_and_export(name): if not shared.Settings.MINIMAL_RUNTIME: shared.Settings.EXPORTED_RUNTIME_METHODS += ['ExitStatus'] - # stack check: - if shared.Settings.STACK_OVERFLOW_CHECK: - shared.Settings.EXPORTED_RUNTIME_METHODS += ['writeStackCookie', 'checkStackCookie'] - if shared.Settings.LINKABLE: exit_with_error('-s LINKABLE=1 is not supported with -s USE_PTHREADS>0!') if shared.Settings.SIDE_MODULE: @@ -1890,6 +1900,9 @@ def include_and_export(name): cflags.append('-D__EMSCRIPTEN_TRACING__=1') if shared.Settings.ALLOW_MEMORY_GROWTH: shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['emscripten_trace_report_memory_layout'] + shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_stack_get_current', + '_emscripten_stack_get_base', + '_emscripten_stack_get_end'] if shared.Settings.USE_PTHREADS: newargs.append('-pthread') diff --git a/emscripten.py b/emscripten.py index d22d5b56df7e7..46d9477844673 100644 --- a/emscripten.py +++ b/emscripten.py @@ -216,12 +216,9 @@ def __init__(self, metadata): def apply_memory(js, memory): # Apply the statically-at-compile-time computed memory locations. # Write it all out + js = js.replace('{{{ HEAP_BASE }}}', str(memory.dynamic_base)) js = js.replace('{{{ STACK_BASE }}}', str(memory.stack_base)) js = js.replace('{{{ STACK_MAX }}}', str(memory.stack_max)) - if shared.Settings.RELOCATABLE: - js = js.replace('{{{ HEAP_BASE }}}', str(memory.dynamic_base)) - - logger.debug('stack_base: %d, stack_max: %d, dynamic_base: %d, static bump: %d', memory.stack_base, memory.stack_max, memory.dynamic_base, memory.static_bump) return js @@ -382,6 +379,7 @@ def emscript(infile, outfile_js, memfile, temp_files, DEBUG): update_settings_glue(metadata, DEBUG) memory = Memory(metadata) + logger.debug('stack_base: %d, stack_max: %d, dynamic_base: %d, static bump: %d', memory.stack_base, memory.stack_max, memory.dynamic_base, memory.static_bump) shared.Settings.LEGACY_DYNAMIC_BASE = memory.dynamic_base if not outfile_js: @@ -418,7 +416,9 @@ def emscript(infile, outfile_js, memfile, temp_files, DEBUG): pre += '\n' + global_initializers + '\n' - pre = apply_memory(pre, memory) + if shared.Settings.RELOCATABLE: + pre = apply_memory(pre, memory) + post = apply_memory(post, memory) pre = apply_static_code_hooks(pre) # In regular runtime, atinits etc. exist in the preamble part post = apply_static_code_hooks(post) # In MINIMAL_RUNTIME, atinit exists in the postamble part @@ -771,7 +771,10 @@ def make_export_wrappers(exports, delay_assignment): wrappers = [] for name in exports: mangled = asmjs_mangle(name) - if shared.Settings.ASSERTIONS: + # The emscripten stack functions are called very early (by writeStackCookie) before + # the runtime is initialized so we can't create these wrappers that check for + # runtimeInitialized. + if shared.Settings.ASSERTIONS and not name.startswith('emscripten_stack_'): # With assertions enabled we create a wrapper that are calls get routed through, for # the lifetime of the program. if delay_assignment: diff --git a/src/library.js b/src/library.js index 3340bdb8bedc2..2ae663005a4b3 100644 --- a/src/library.js +++ b/src/library.js @@ -3653,12 +3653,6 @@ LibraryManager.library = { // special runtime support - emscripten_scan_stack: function(func) { - var base = STACK_BASE; // TODO verify this is right on pthreads - var end = stackSave(); - {{{ makeDynCall('vii', 'func') }}}(Math.min(base, end), Math.max(base, end)); - }, - // Used by wasm-emscripten-finalize to implement STACK_OVERFLOW_CHECK __handle_stack_overflow: function() { abort('stack overflow') diff --git a/src/library_async.js b/src/library_async.js index b55e7cae84d11..33b4a0eee04ad 100644 --- a/src/library_async.js +++ b/src/library_async.js @@ -381,11 +381,12 @@ mergeInto(LibraryManager.library, { * NOTE: This function is the asynchronous part of emscripten_fiber_swap. */ finishContextSwitch: function(newFiber) { - STACK_BASE = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.stack_base, 'i32') }}}; - STACK_MAX = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.stack_limit, 'i32') }}}; + var stack_base = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.stack_base, 'i32') }}}; + var stack_max = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.stack_limit, 'i32') }}}; + _emscripten_stack_set_limits(stack_base, stack_max); #if STACK_OVERFLOW_CHECK >= 2 - Module['___set_stack_limits'](STACK_BASE, STACK_MAX); + Module['___set_stack_limits'](stack_base, stack_max); #endif stackRestore({{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.stack_ptr, 'i32') }}}); @@ -440,8 +441,8 @@ mergeInto(LibraryManager.library, { emscripten_fiber_init_from_current_context__sig: 'vii', emscripten_fiber_init_from_current_context__deps: ['$Asyncify'], emscripten_fiber_init_from_current_context: function(fiber, asyncStack, asyncStackSize) { - {{{ makeSetValue('fiber', C_STRUCTS.emscripten_fiber_s.stack_base, 'STACK_BASE', 'i32') }}}; - {{{ makeSetValue('fiber', C_STRUCTS.emscripten_fiber_s.stack_limit, 'STACK_MAX', 'i32') }}}; + {{{ makeSetValue('fiber', C_STRUCTS.emscripten_fiber_s.stack_base, '_emscripten_stack_get_base()', 'i32') }}}; + {{{ makeSetValue('fiber', C_STRUCTS.emscripten_fiber_s.stack_limit, '_emscripten_stack_get_end()', 'i32') }}}; {{{ makeSetValue('fiber', C_STRUCTS.emscripten_fiber_s.entry, 0, 'i32') }}}; var asyncifyData = fiber + {{{ C_STRUCTS.emscripten_fiber_s.asyncify_data }}}; diff --git a/src/library_pthread.js b/src/library_pthread.js index 7a84f872940a7..fa4a95fc8f6fc 100644 --- a/src/library_pthread.js +++ b/src/library_pthread.js @@ -1462,11 +1462,9 @@ var LibraryPThread = { }, $establishStackSpace: function(stackTop, stackMax) { - STACK_BASE = STACKTOP = stackTop; - STACK_MAX = stackMax; - + _emscripten_stack_set_limits(stackTop, stackMax); #if STACK_OVERFLOW_CHECK >= 2 - ___set_stack_limits(STACK_BASE, STACK_MAX); + ___set_stack_limits(_emscripten_stack_get_base(), _emscripten_stack_get_end()); #endif // Call inside wasm module to set up the stack frame for this pthread in asm.js/wasm module scope diff --git a/src/library_pthread_stub.js b/src/library_pthread_stub.js index 023d90b416657..36466e1a4639c 100644 --- a/src/library_pthread_stub.js +++ b/src/library_pthread_stub.js @@ -59,8 +59,8 @@ var LibraryPThreadStub = { void **restrict stackaddr, size_t *restrict stacksize); */ /*FIXME: assumes that there is only one thread, and that attr is the current thread*/ - {{{ makeSetValue('stackaddr', '0', 'STACK_BASE', 'i8*') }}}; - {{{ makeSetValue('stacksize', '0', 'TOTAL_STACK', 'i32') }}}; + {{{ makeSetValue('stackaddr', '0', '_emscripten_stack_get_base()', 'i8*') }}}; + {{{ makeSetValue('stacksize', '0', TOTAL_STACK, 'i32') }}}; return 0; }, pthread_attr_getdetachstate: function(attr, detachstate) { diff --git a/src/library_stack.js b/src/library_stack.js index e87b4046b1a67..b7ceac5e7e879 100644 --- a/src/library_stack.js +++ b/src/library_stack.js @@ -5,16 +5,8 @@ */ mergeInto(LibraryManager.library, { - emscripten_stack_get_base: function() { - return STACK_BASE; - }, - emscripten_stack_get_end: function() { - // TODO(sbc): rename STACK_MAX -> STACK_END? - return STACK_MAX; - }, - $abortStackOverflow__import: true, $abortStackOverflow: function(allocSize) { - abort('Stack overflow! Attempted to allocate ' + allocSize + ' bytes on the stack, but stack has only ' + (STACK_MAX - stackSave() + allocSize) + ' bytes available!'); + abort('Stack overflow! Attempted to allocate ' + allocSize + ' bytes on the stack, but stack has only ' + (_emscripten_stack_get_free() + allocSize) + ' bytes available!'); }, }); diff --git a/src/library_trace.js b/src/library_trace.js index e7ee2a9a2a501..272eb10a3b9aa 100644 --- a/src/library_trace.js +++ b/src/library_trace.js @@ -263,9 +263,9 @@ var LibraryTracing = { if (EmscriptenTrace.postEnabled) { var memory_layout = { 'static_base': {{{ GLOBAL_BASE }}}, - 'stack_base': STACK_BASE, - 'stack_top': STACKTOP, - 'stack_max': STACK_MAX, + 'stack_base': _emscripten_stack_get_base(), + 'stack_top': _emscripten_stack_get_current(), + 'stack_max': _emscripten_stack_get_end(), 'dynamic_top': _sbrk(), 'total_memory': HEAP8.length }; diff --git a/src/memoryprofiler.js b/src/memoryprofiler.js index 0c8587b678ca1..833494e8432e7 100644 --- a/src/memoryprofiler.js +++ b/src/memoryprofiler.js @@ -70,7 +70,7 @@ var emscriptenMemoryProfiler = { totalTimesMallocCalled: 0, totalTimesFreeCalled: 0, - // Tracks the highest seen location of the STACKTOP variable. + // Tracks the highest seen location of the stack pointer. stackTopWatermark: Infinity, // The canvas DOM element to which to draw the allocation map. @@ -161,8 +161,10 @@ var emscriptenMemoryProfiler = { }, recordStackWatermark: function() { - var self = emscriptenMemoryProfiler; - self.stackTopWatermark = Math.min(self.stackTopWatermark, STACKTOP); + if (runtimeInitialized) { + var self = emscriptenMemoryProfiler; + self.stackTopWatermark = Math.min(self.stackTopWatermark, _emscripten_stack_get_current()); + } }, onMalloc: function onMalloc(ptr, size) { @@ -485,28 +487,25 @@ var emscriptenMemoryProfiler = { self.canvas.width = document.documentElement.clientWidth - 32; } + if (!runtimeInitialized) { + return; + } + var stackBase = _emscripten_stack_get_base(); + var stackMax = _emscripten_stack_get_end(); + var stackCurrent = _emscripten_stack_get_current(); var width = (nBits(HEAP8.length) + 3) / 4; // Pointer 'word width' var html = 'Total HEAP size: ' + self.formatBytes(HEAP8.length) + '.'; - html += '
' + colorBar('#202020') + 'STATIC memory area size: ' + self.formatBytes(Math.min(STACK_BASE, STACK_MAX) - {{{ GLOBAL_BASE }}}); + html += '
' + colorBar('#202020') + 'STATIC memory area size: ' + self.formatBytes(stackMax - {{{ GLOBAL_BASE }}}); html += '. {{{ GLOBAL_BASE }}}: ' + toHex({{{ GLOBAL_BASE }}}, width); - html += '
' + colorBar('#FF8080') + 'STACK memory area size: ' + self.formatBytes(Math.abs(STACK_MAX - STACK_BASE)); - html += '. STACK_BASE: ' + toHex(STACK_BASE, width); - html += '. STACKTOP: ' + toHex(STACKTOP, width); - html += '. STACK_MAX: ' + toHex(STACK_MAX, width) + '.'; - html += '
STACK memory area used now (should be zero): ' + self.formatBytes(STACKTOP - STACK_BASE) + '.' + colorBar('#FFFF00') + ' STACK watermark highest seen usage (approximate lower-bound!): ' + self.formatBytes(Math.abs(self.stackTopWatermark - STACK_BASE)); + html += '
' + colorBar('#FF8080') + 'STACK memory area size: ' + self.formatBytes(stackBase - stackMax); + html += '. STACK_BASE: ' + toHex(stackBase, width); + html += '. STACKTOP: ' + toHex(stackCurrent, width); + html += '. STACK_MAX: ' + toHex(stackMax, width) + '.'; + html += '
STACK memory area used now (should be zero): ' + self.formatBytes(stackBase - stackCurrent) + '.' + colorBar('#FFFF00') + ' STACK watermark highest seen usage (approximate lower-bound!): ' + self.formatBytes(stackBase - self.stackTopWatermark); - if (runtimeInitialized) { - // During startup sbrk may not be defined yet. Ideally we should probably - // refactor memoryprofiler so that it only gets here after compiled code is - // ready to be called. For now, if the runtime is not yet initialized, - // assume the brk is right after the stack. - var heap_base = Module['___heap_base']; - var heap_end = _sbrk(); - } else { - var heap_base = STACK_BASE; - var heap_end = STACK_BASE; - } + var heap_base = Module['___heap_base']; + var heap_end = _sbrk(); html += "
DYNAMIC memory area size: " + self.formatBytes(heap_end - heap_base); html += ". start: " + toHex(heap_base, width); html += ". end: " + toHex(heap_end, width) + "."; @@ -525,13 +524,13 @@ var emscriptenMemoryProfiler = { self.drawContext.fillRect(0, 0, self.canvas.width, self.canvas.height); self.drawContext.fillStyle = "#FF8080"; - self.fillLine(STACK_BASE, STACK_MAX); + self.fillLine(stackMax, stackBase); self.drawContext.fillStyle = "#FFFF00"; - self.fillLine(Math.min(STACK_BASE, self.stackTopWatermark), Math.max(STACK_BASE, self.stackTopWatermark)); + self.fillLine(self.stackTopWatermark, stackBase); self.drawContext.fillStyle = "#FF0000"; - self.fillLine(Math.min(STACK_BASE, STACKTOP), Math.max(STACK_BASE, STACKTOP)); + self.fillLine(stackCurrent, stackBase); self.drawContext.fillStyle = "#70FF70"; self.fillLine(heap_base, heap_end); diff --git a/src/postamble.js b/src/postamble.js index d83d2ead0df44..8f0c708138b2b 100644 --- a/src/postamble.js +++ b/src/postamble.js @@ -239,6 +239,15 @@ function run(args) { } #if STACK_OVERFLOW_CHECK + // This is normally called automatically during __wasm_call_ctors but need to + // get these values before even running any of the ctors so we call it redundantly + // here. + // TODO(sbc): Move writeStackCookie to native to to avoid this. +#if RELOCATABLE + _emscripten_stack_set_limits({{{ getQuoted('STACK_BASE') }}}, {{{ getQuoted('STACK_MAX') }}}); +#else + _emscripten_stack_init(); +#endif writeStackCookie(); #endif diff --git a/src/postamble_minimal.js b/src/postamble_minimal.js index 9f999844cf779..03d70a2161011 100644 --- a/src/postamble_minimal.js +++ b/src/postamble_minimal.js @@ -14,7 +14,7 @@ function run() { #endif #if STACK_OVERFLOW_CHECK >= 2 - ___set_stack_limits(STACK_BASE, STACK_MAX); + ___set_stack_limits(_emscripten_stack_get_base(), _emscripten_stack_get_end()); #endif #if PROXY_TO_PTHREAD @@ -77,6 +77,7 @@ function initRuntime(asm) { #endif #if STACK_OVERFLOW_CHECK + _emscripten_stack_init(); writeStackCookie(); #endif diff --git a/src/preamble.js b/src/preamble.js index 9e54d0f811ec4..09b1c2341905b 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -281,17 +281,8 @@ function updateGlobalBufferAndViews(buf) { Module['HEAPF64'] = HEAPF64 = new Float64Array(buf); } -var STACK_BASE = {{{ getQuoted('STACK_BASE') }}}, - STACKTOP = STACK_BASE, - STACK_MAX = {{{ getQuoted('STACK_MAX') }}}; - - -#if ASSERTIONS -assert(STACK_BASE % 16 === 0, 'stack must start aligned'); -#endif - #if RELOCATABLE -var __stack_pointer = new WebAssembly.Global({value: 'i32', mutable: true}, STACK_BASE); +var __stack_pointer = new WebAssembly.Global({value: 'i32', mutable: true}, {{{ getQuoted('STACK_BASE') }}}); // To support such allocations during startup, track them on __heap_base and // then when the main module is loaded it reads that value and uses it to @@ -301,18 +292,6 @@ var __stack_pointer = new WebAssembly.Global({value: 'i32', mutable: true}, STAC Module['___heap_base'] = {{{ getQuoted('HEAP_BASE') }}}; #endif // RELOCATABLE -#if USE_PTHREADS -if (ENVIRONMENT_IS_PTHREAD) { - // At the 'load' stage of Worker startup, we are just loading this script - // but not ready to run yet. At 'run' we receive proper values for the stack - // etc. and can launch a pthread. Set some fake values there meanwhile to - // catch bugs, then set the real values in establishStackSpace later. -#if ASSERTIONS || STACK_OVERFLOW_CHECK >= 2 - STACK_MAX = STACKTOP = STACK_MAX = 0x7FFFFFFF; -#endif -} -#endif - var TOTAL_STACK = {{{ TOTAL_STACK }}}; #if ASSERTIONS if (Module['TOTAL_STACK']) assert(TOTAL_STACK === Module['TOTAL_STACK'], 'the stack size can no longer be determined at runtime') @@ -402,7 +381,7 @@ function initRuntime() { #endif runtimeInitialized = true; #if STACK_OVERFLOW_CHECK >= 2 - Module['___set_stack_limits'](STACK_BASE, STACK_MAX); + Module['___set_stack_limits'](_emscripten_stack_get_base(), _emscripten_stack_get_end()); #endif {{{ getQuoted('ATINITS') }}} callRuntimeCallbacks(__ATINIT__); diff --git a/src/preamble_minimal.js b/src/preamble_minimal.js index 1793d6a27600e..bf3e87ab789d4 100644 --- a/src/preamble_minimal.js +++ b/src/preamble_minimal.js @@ -59,12 +59,6 @@ Module['wasm'] = base64Decode('{{{ getQuoted("WASM_BINARY_DATA") }}}'); if (!ENVIRONMENT_IS_PTHREAD) { #endif -var TOTAL_STACK = {{{ TOTAL_STACK }}}, - STACK_BASE = {{{ getQuoted('STACK_BASE') }}}, - STACKTOP = STACK_BASE, - STACK_MAX = {{{ getQuoted('STACK_MAX') }}} - ; - #if ALLOW_MEMORY_GROWTH && MAXIMUM_MEMORY != -1 var wasmMaximumMemory = {{{ MAXIMUM_MEMORY >>> 16 }}}; #else @@ -96,8 +90,7 @@ var WASM_PAGE_SIZE = {{{ WASM_PAGE_SIZE }}}; #if USE_PTHREADS if (!ENVIRONMENT_IS_PTHREAD) { #endif -assert(STACK_BASE % 16 === 0, 'stack must start aligned to 16 bytes, STACK_BASE==' + STACK_BASE); -assert({{{ INITIAL_MEMORY }}} >= TOTAL_STACK, 'INITIAL_MEMORY should be larger than TOTAL_STACK, was ' + {{{ INITIAL_MEMORY }}} + '! (TOTAL_STACK=' + TOTAL_STACK + ')'); +assert({{{ INITIAL_MEMORY }}} >= {{{ TOTAL_STACK }}}, 'INITIAL_MEMORY should be larger than TOTAL_STACK, was ' + {{{ INITIAL_MEMORY }}} + '! (TOTAL_STACK=' + {{{ TOTAL_STACK }}} + ')'); assert({{{ INITIAL_MEMORY }}} % WASM_PAGE_SIZE === 0); #if MAXIMUM_MEMORY != -1 assert({{{ MAXIMUM_MEMORY }}} % WASM_PAGE_SIZE == 0); diff --git a/src/runtime_safe_heap.js b/src/runtime_safe_heap.js index df648bdb8bdb8..f545b5930402f 100644 --- a/src/runtime_safe_heap.js +++ b/src/runtime_safe_heap.js @@ -111,7 +111,7 @@ function SAFE_HEAP_STORE(dest, value, bytes, isFloat) { if (runtimeInitialized) { var brk = _sbrk() >>> 0; if (dest + bytes > brk) abort('segmentation fault, exceeded the top of the available dynamic heap when storing ' + bytes + ' bytes to address ' + dest + '. DYNAMICTOP=' + brk); - assert(brk >= STACK_BASE); // sbrk-managed memory must be above the stack + assert(brk >= _emscripten_stack_get_base()); // sbrk-managed memory must be above the stack assert(brk <= HEAP8.length); } setValue(dest, value, getSafeHeapType(bytes, isFloat), 1); @@ -131,7 +131,7 @@ function SAFE_HEAP_LOAD(dest, bytes, unsigned, isFloat) { if (runtimeInitialized) { var brk = _sbrk() >>> 0; if (dest + bytes > brk) abort('segmentation fault, exceeded the top of the available dynamic heap when loading ' + bytes + ' bytes from address ' + dest + '. DYNAMICTOP=' + brk); - assert(brk >= STACK_BASE); // sbrk-managed memory must be above the stack + assert(brk >= _emscripten_stack_get_base()); // sbrk-managed memory must be above the stack assert(brk <= HEAP8.length); } var type = getSafeHeapType(bytes, isFloat); diff --git a/src/runtime_stack_check.js b/src/runtime_stack_check.js index 97a848cbf0c8e..879b631fb16e1 100644 --- a/src/runtime_stack_check.js +++ b/src/runtime_stack_check.js @@ -7,12 +7,13 @@ #if STACK_OVERFLOW_CHECK // Initializes the stack cookie. Called at the startup of main and at the startup of each thread in pthreads mode. function writeStackCookie() { + var max = _emscripten_stack_get_end(); #if ASSERTIONS - assert((STACK_MAX & 3) == 0); + assert((max & 3) == 0); #endif // The stack grows downwards - HEAPU32[(STACK_MAX >> 2)+1] = 0x2135467; - HEAPU32[(STACK_MAX >> 2)+2] = 0x89BACDFE; + HEAPU32[(max >> 2)+1] = 0x2135467; + HEAPU32[(max >> 2)+2] = 0x89BACDFE; #if !USE_ASAN && !SAFE_HEAP // ASan and SAFE_HEAP check address 0 themselves // Also test the global address 0 for integrity. HEAP32[0] = 0x63736d65; /* 'emsc' */ @@ -23,8 +24,9 @@ function checkStackCookie() { #if !MINIMAL_RUNTIME if (ABORT) return; #endif - var cookie1 = HEAPU32[(STACK_MAX >> 2)+1]; - var cookie2 = HEAPU32[(STACK_MAX >> 2)+2]; + var max = _emscripten_stack_get_end(); + var cookie1 = HEAPU32[(max >> 2)+1]; + var cookie2 = HEAPU32[(max >> 2)+2]; if (cookie1 != 0x2135467 || cookie2 != 0x89BACDFE) { abort('Stack overflow! Stack cookie has been overwritten, expected hex dwords 0x89BACDFE and 0x2135467, but received 0x' + cookie2.toString(16) + ' ' + cookie1.toString(16)); } diff --git a/src/shell_minimal.js b/src/shell_minimal.js index d132e5906f6cd..2f3597ea712f5 100644 --- a/src/shell_minimal.js +++ b/src/shell_minimal.js @@ -143,9 +143,6 @@ var ENVIRONMENT_IS_WORKER = ENVIRONMENT_IS_PTHREAD = typeof importScripts === 'f #if MODULARIZE if (ENVIRONMENT_IS_WORKER) { var buffer = {{{EXPORT_NAME}}}.buffer; - var STACK_BASE = {{{EXPORT_NAME}}}.STACK_BASE; - var STACKTOP = {{{EXPORT_NAME}}}.STACKTOP; - var STACK_MAX = {{{EXPORT_NAME}}}.STACK_MAX; } #endif diff --git a/src/shell_pthreads.js b/src/shell_pthreads.js index 16be15eae80d5..e909409d5e842 100644 --- a/src/shell_pthreads.js +++ b/src/shell_pthreads.js @@ -14,7 +14,6 @@ var ENVIRONMENT_IS_PTHREAD = Module['ENVIRONMENT_IS_PTHREAD'] || false; if (ENVIRONMENT_IS_PTHREAD) { // Grab imports from the pthread to local scope. buffer = Module['buffer']; - // Note that not all runtime fields are imported above. Values for STACK_BASE, STACKTOP and STACK_MAX are not yet known at worker.js load time. - // These will be filled in at pthread startup time (the 'run' message for a pthread - pthread start establishes the stack frame) + // Note that not all runtime fields are imported above } diff --git a/system/include/emscripten/stack.h b/system/include/emscripten/stack.h index 03165bcfada22..36060a6c1d40d 100644 --- a/system/include/emscripten/stack.h +++ b/system/include/emscripten/stack.h @@ -7,6 +7,9 @@ #pragma once +#include +#include + // API that gives access to introspecting the Wasm data stack. Build with // -lstack.js to use this API. @@ -26,17 +29,21 @@ uintptr_t emscripten_stack_get_base(void); // emscripten_stack_get_base(). uintptr_t emscripten_stack_get_end(void); +// Setup internal base/end values based on the initial values that were either +// set at compile time (in static linking) or instantiations time (for dynamic +// linking). +void emscripten_stack_init(void); + +// Sets the internal values reported by emscripten_stack_get_base and +// emscripten_stack_get_end. This should only used by low level libraries +// such as asyncify fibres. +void emscripten_stack_set_limits(void* base, void* end); + // Returns the current stack pointer. uintptr_t emscripten_stack_get_current(void); -// Setup internal state such that emscripten_stack_get_free can be used. -// This needed until we can fix: -// https://github.com/emscripten-core/emscripten/issues/11773 -void emscripten_stack_init(void); - // Returns the number of free bytes left on the stack. This is required to be -// fast so that it can be called frequently and requires `emscripten_stack_init` -// to be called before it will return accurate values. +// fast so that it can be called frequently. size_t emscripten_stack_get_free(void); #ifdef __cplusplus diff --git a/system/lib/compiler-rt/lib/sanitizer_common/sanitizer_emscripten.cpp b/system/lib/compiler-rt/lib/sanitizer_common/sanitizer_emscripten.cpp index 16a16be9c789f..4c52ff5294db3 100644 --- a/system/lib/compiler-rt/lib/sanitizer_common/sanitizer_emscripten.cpp +++ b/system/lib/compiler-rt/lib/sanitizer_common/sanitizer_emscripten.cpp @@ -22,6 +22,7 @@ #if SANITIZER_EMSCRIPTEN #include +#include namespace __sanitizer { @@ -89,8 +90,8 @@ uptr internal_munmap(void *addr, uptr length) { void GetThreadStackTopAndBottom(bool at_initialization, uptr *stack_top, uptr *stack_bottom) { - *stack_top = EM_ASM_INT({ return STACK_BASE; }); - *stack_bottom = EM_ASM_INT({ return STACK_MAX; }); + *stack_top = emscripten_stack_get_base(); + *stack_bottom = emscripten_stack_get_end(); } char *fake_argv[] = {0}; diff --git a/system/lib/compiler-rt/stack_limits.S b/system/lib/compiler-rt/stack_limits.S new file mode 100644 index 0000000000000..e17555b76b686 --- /dev/null +++ b/system/lib/compiler-rt/stack_limits.S @@ -0,0 +1,73 @@ +.globl emscripten_stack_init +.globl emscripten_stack_set_limits +.globl emscripten_stack_get_free +.globl emscripten_stack_get_base +.globl emscripten_stack_get_end + +.globaltype __stack_pointer, i32 + +# TODO(sbc): It would be nice if these we initialized directly +# using i32.const rather than using the `emscripten_stack_init` +.globaltype __stack_end, i32 +__stack_end: +.globaltype __stack_base, i32 +__stack_base: + +emscripten_stack_get_base: + .functype emscripten_stack_get_base () -> (i32) + global.get __stack_base + end_function + +emscripten_stack_get_end: + .functype emscripten_stack_get_end () -> (i32) + global.get __stack_end + end_function + +emscripten_stack_init: + # Initialize __stack_end and __stack_base. + # This must be called before emscripten_stack_get_end, + # emscripten_stack_get_base, or emscripten_stack_get_free are called + .functype emscripten_stack_init () -> () + + # The heap base is where the stack grown down from. +#ifdef __PIC__ + global.get __heap_base@GOT +#else + i32.const __heap_base +#endif + global.set __stack_base + + # The end of stack data is the limit of the stack growth +#ifdef __PIC__ + global.get __data_end@GOT +#else + i32.const __data_end +#endif + # Align up to 16 bytes + i32.const 0xf + i32.add + i32.const -0x10 + i32.and + global.set __stack_end + + end_function + +emscripten_stack_set_limits: + .functype emscripten_stack_set_limits (i32, i32) -> () + local.get 0 + global.set __stack_base + local.get 1 + global.set __stack_end + end_function + +emscripten_stack_get_free: + .functype emscripten_stack_get_free () -> (i32) + global.get __stack_pointer + global.get __stack_end + i32.sub + end_function + +# Add emscripten_stack_init to static ctors +.section .init_array.1,"",@ +.p2align 2 +.int32 emscripten_stack_init diff --git a/system/lib/compiler-rt/stack_ops.s b/system/lib/compiler-rt/stack_ops.s index 6e290bd6b284c..5ac175b55fc1a 100644 --- a/system/lib/compiler-rt/stack_ops.s +++ b/system/lib/compiler-rt/stack_ops.s @@ -1,9 +1,7 @@ .globl stackSave .globl stackRestore .globl stackAlloc -.globl emscripten_stack_init .globl emscripten_stack_get_current -.globl emscripten_stack_get_free .globaltype __stack_pointer, i32 @@ -39,22 +37,3 @@ emscripten_stack_get_current: global.get __stack_pointer end_function -.functype emscripten_stack_get_end () -> (i32) -.functype emscripten_stack_get_base () -> (i32) -.globaltype __stack_end, i32 -__stack_end: - -emscripten_stack_init: - # initialize __stack_end such that future calls to emscripten_stack_get_free - # use the correct value. - .functype emscripten_stack_init () -> () - call emscripten_stack_get_end - global.set __stack_end - end_function - -emscripten_stack_get_free: - .functype emscripten_stack_get_free () -> (i32) - global.get __stack_pointer - global.get __stack_end - i32.sub - end_function diff --git a/system/lib/libc/emscripten_scan_stack.c b/system/lib/libc/emscripten_scan_stack.c new file mode 100644 index 0000000000000..c5a4417830c83 --- /dev/null +++ b/system/lib/libc/emscripten_scan_stack.c @@ -0,0 +1,15 @@ +/* + * Copyright 2020 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include + +void emscripten_scan_stack(em_scan_func func) { + uintptr_t base = emscripten_stack_get_base(); + uintptr_t end = emscripten_stack_get_current(); + func((void*)end, (void*)base); +} diff --git a/system/lib/pthread/library_pthread.c b/system/lib/pthread/library_pthread.c index 09d778b3bc0f0..447aff8ddbc35 100644 --- a/system/lib/pthread/library_pthread.c +++ b/system/lib/pthread/library_pthread.c @@ -10,8 +10,6 @@ #include "../internal/pthread_impl.h" #include #include -#include -#include #include #include #include @@ -30,6 +28,10 @@ #include #include +#include +#include +#include + // With LLVM 3.6, C11 is the default compilation mode. // gets() is deprecated under that standard, but emcc // still provides it, so always include it in the build. @@ -938,9 +940,9 @@ int proxy_main(int argc, char** argv) { if (emscripten_has_threading_support()) { pthread_attr_t attr; pthread_attr_init(&attr); - // Use TOTAL_STACK for the stack size, which is the normal size of the stack + // Use the size of the current stack, which is the normal size of the stack // that main() would have without PROXY_TO_PTHREAD. - pthread_attr_setstacksize(&attr, EM_ASM_INT({ return TOTAL_STACK })); + pthread_attr_setstacksize(&attr, emscripten_stack_get_base() - emscripten_stack_get_end()); // Pass special ID -1 to the list of transferred canvases to denote that the thread creation // should instead take a list of canvases that are specified from the command line with // -s OFFSCREENCANVASES_TO_PTHREAD linker flag. diff --git a/tests/other/metadce/hello_world.exports b/tests/other/metadce/hello_world.exports index 5f7cc9d9a168d..7afd5142b4d66 100644 --- a/tests/other/metadce/hello_world.exports +++ b/tests/other/metadce/hello_world.exports @@ -3,6 +3,9 @@ __errno_location __indirect_function_table __wasm_call_ctors dynCall_jiji +emscripten_stack_get_end +emscripten_stack_get_free +emscripten_stack_init fflush main stackAlloc diff --git a/tests/other/metadce/hello_world.funcs b/tests/other/metadce/hello_world.funcs index 0519ab8062648..1f3492f148a0e 100644 --- a/tests/other/metadce/hello_world.funcs +++ b/tests/other/metadce/hello_world.funcs @@ -21,6 +21,9 @@ $__vfprintf_internal $__wasi_syscall_ret $__wasm_call_ctors $dynCall_jiji +$emscripten_stack_get_end +$emscripten_stack_get_free +$emscripten_stack_init $fflush $fmt_fp $fmt_o diff --git a/tests/other/metadce/minimal.exports b/tests/other/metadce/minimal.exports index b3b597a3180a5..36df72eae0232 100644 --- a/tests/other/metadce/minimal.exports +++ b/tests/other/metadce/minimal.exports @@ -3,6 +3,9 @@ __errno_location __indirect_function_table __wasm_call_ctors add +emscripten_stack_get_end +emscripten_stack_get_free +emscripten_stack_init fflush global_val stackAlloc diff --git a/tests/other/metadce/minimal.funcs b/tests/other/metadce/minimal.funcs index 552a4034d2ed3..6bd57c99366d4 100644 --- a/tests/other/metadce/minimal.funcs +++ b/tests/other/metadce/minimal.funcs @@ -8,6 +8,9 @@ $__unlock $__unlockfile $__wasm_call_ctors $add +$emscripten_stack_get_end +$emscripten_stack_get_free +$emscripten_stack_init $fflush $stackAlloc $stackRestore diff --git a/tests/other/test_proxy_to_pthread_stack.c b/tests/other/test_proxy_to_pthread_stack.c index 344a2144eeffb..05d4571a53533 100644 --- a/tests/other/test_proxy_to_pthread_stack.c +++ b/tests/other/test_proxy_to_pthread_stack.c @@ -1,6 +1,7 @@ #include #include #include + int main(void) { int32_t data[64*1024]; EM_ASM({ console.log($0, "success"); }, data); diff --git a/tests/pthread/test_pthread_stack_bounds.cpp b/tests/pthread/test_pthread_stack_bounds.cpp index 0155c6c5b7ed6..255088630ec2e 100644 --- a/tests/pthread/test_pthread_stack_bounds.cpp +++ b/tests/pthread/test_pthread_stack_bounds.cpp @@ -1,16 +1,13 @@ #include #include +#include void thread(void) { bool passed; - size_t stack_base = EM_ASM_INT({ return STACK_BASE; }); - size_t stack_max = EM_ASM_INT({ return STACK_MAX; }); + size_t stack_base = emscripten_stack_get_base(); + size_t stack_max = emscripten_stack_get_end(); size_t current = (size_t) &passed; -#if __asmjs__ - passed = stack_base < current && current < stack_max; -#else passed = stack_base > current && current > stack_max; -#endif #ifdef REPORT_RESULT REPORT_RESULT(passed ? 1 : 0); #endif diff --git a/tests/test_browser.py b/tests/test_browser.py index 71d3f7ce17f51..be11d5399253e 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -3950,7 +3950,7 @@ def test_pthread_utf8_funcs(self): def test_pthread_wake_all(self): self.btest(path_from_root('tests', 'pthread', 'test_futex_wake_all.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'INITIAL_MEMORY=64MB', '-s', 'NO_EXIT_RUNTIME=1'], also_asmjs=True) - # Test that STACK_BASE and STACK_MAX correctly bound the stack on pthreads. + # Test that stack base and max correctly bound the stack on pthreads. @requires_threads def test_pthread_stack_bounds(self): self.btest(path_from_root('tests', 'pthread', 'test_pthread_stack_bounds.cpp'), expected='1', args=['-s', 'USE_PTHREADS']) diff --git a/tests/test_core.py b/tests/test_core.py index 1ff9577272d42..2fe1b834ee9fb 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -5088,6 +5088,10 @@ def test_fs_base(self): @also_with_noderawfs @is_slow_test def test_fs_nodefs_rw(self): + # TODO(sbc): This test exposes in issue in the way we run closure compiler and + # causes it to generate non-ES5 output. + # Remove this line once we fix: https://github.com/emscripten-core/emscripten/issues/12628 + self.uses_es6 = True self.emcc_args += ['-lnodefs.js'] self.set_setting('SYSCALL_DEBUG', 1) self.do_runf(path_from_root('tests', 'fs', 'test_nodefs_rw.c'), 'success') @@ -7568,9 +7572,9 @@ def test_memprof_requirements(self): create_test_file('lib.js', ''' mergeInto(LibraryManager.library, { check_memprof_requirements: function() { - if (typeof STACK_BASE === 'number' && - typeof STACK_MAX === 'number' && - typeof STACKTOP === 'number' && + if (typeof _emscripten_stack_get_base === 'function' && + typeof _emscripten_stack_get_end === 'function' && + typeof _emscripten_stack_get_current === 'function' && typeof Module['___heap_base'] === 'number') { out('able to run memprof'); } else { diff --git a/tests/test_other.py b/tests/test_other.py index 4bc6da1ac0494..752fa7a84a6e4 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -6672,7 +6672,7 @@ def strip_numeric_suffixes(funcname): self.assertFileContents(filename, data) @parameterized({ - 'O0': ([], [], ['waka'], 743), # noqa + 'O0': ([], [], ['waka'], 847), # noqa 'O1': (['-O1'], [], ['waka'], 303), # noqa 'O2': (['-O2'], [], ['waka'], 265), # noqa # in -O3, -Os and -Oz we metadce, and they shrink it down to the minimal output we want diff --git a/tools/system_libs.py b/tools/system_libs.py index 6e9fe4bd03c11..960278c6947f8 100755 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -149,6 +149,7 @@ def get_wasm_libc_rt_files(): other_files = files_in_path( path_components=['system', 'lib', 'libc'], filenames=['emscripten_memcpy.c', 'emscripten_memset.c', + 'emscripten_scan_stack.c', 'emscripten_memmove.c']) # Calls to iprintf can be generated during codegen. Ideally we wouldn't # compile these with -O2 like we do the rest of compiler-rt since its @@ -644,6 +645,7 @@ class libcompiler_rt(MTLibrary): src_dir = ['system', 'lib', 'compiler-rt', 'lib', 'builtins'] src_files = glob_in_path(src_dir, '*.c') src_files.append(shared.path_from_root('system', 'lib', 'compiler-rt', 'stack_ops.s')) + src_files.append(shared.path_from_root('system', 'lib', 'compiler-rt', 'stack_limits.S')) src_files.append(shared.path_from_root('system', 'lib', 'compiler-rt', 'emscripten_setjmp.c')) src_files.append(shared.path_from_root('system', 'lib', 'compiler-rt', 'emscripten_exception_builtins.c'))