From 1b58a8079817583f302c2427cf253a3242c2df80 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Wed, 17 Jun 2020 16:07:45 -0700 Subject: [PATCH] Add functions for access emscripten stack layout information (#11437) This change adds emscripten_stack_get_base/free/end/current. This is a cut down version of #11162 which uses wasm assembly rather than relying on binaryen changes. Also remove old emscripten_get_stack_base/emscripten_get_stack_top from src/library.js. emscripten_get_stack_top never worked anyway since its was not marked as asm and therefore not referencing the arm-internal version of STACKTOP. --- src/library.js | 8 ----- src/library_stack.js | 25 ++++++++++++++-- system/include/emscripten/stack.h | 37 +++++++++++++++++++++++ system/lib/compiler-rt/stack_ops.s | 26 +++++++++++++++++ tests/core/test_stack_get_free.c | 47 ++++++++++++++++++++++++++++++ tests/core/test_stack_get_free.out | 21 +++++++++++++ tests/test_core.py | 9 +++++- 7 files changed, 162 insertions(+), 11 deletions(-) create mode 100644 system/include/emscripten/stack.h create mode 100644 tests/core/test_stack_get_free.c create mode 100644 tests/core/test_stack_get_free.out diff --git a/src/library.js b/src/library.js index 851508eeaaec..1b47ddfefd66 100644 --- a/src/library.js +++ b/src/library.js @@ -4547,14 +4547,6 @@ LibraryManager.library = { }); }, - emscripten_get_stack_top: function() { - return STACKTOP; - }, - - emscripten_get_stack_base: function() { - return STACK_BASE; - }, - _readAsmConstArgsArray: '=[]', $readAsmConstArgs__deps: ['_readAsmConstArgsArray'], $readAsmConstArgs: function(sigPtr, buf) { diff --git a/src/library_stack.js b/src/library_stack.js index 501ba5ce995e..7cb8e63a36f9 100644 --- a/src/library_stack.js +++ b/src/library_stack.js @@ -5,7 +5,15 @@ */ mergeInto(LibraryManager.library, { -#if WASM_BACKEND == 0 + emscripten_stack_get_base: function() { + return STACK_BASE; + }, + emscripten_stack_get_end: function() { + // TODO(sbc): rename STACK_MAX -> STACK_END? + return STACK_MAX; + }, + +#if !WASM_BACKEND $abortStackOverflow__deps: ['$stackSave'], #endif $abortStackOverflow__import: true, @@ -13,7 +21,7 @@ mergeInto(LibraryManager.library, { abort('Stack overflow! Attempted to allocate ' + allocSize + ' bytes on the stack, but stack has only ' + (STACK_MAX - stackSave() + allocSize) + ' bytes available!'); }, -#if WASM_BACKEND == 0 +#if !WASM_BACKEND $stackAlloc__asm: true, $stackAlloc__sig: 'ii', #if ASSERTIONS || STACK_OVERFLOW_CHECK >= 2 @@ -43,5 +51,18 @@ mergeInto(LibraryManager.library, { top = top|0; STACKTOP = top; }, + + // With the wasm backend, these functions are implemented as native + // functions in compiler-rt/stack_ops.s + emscripten_stack_get_current__asm: true, + emscripten_stack_get_current__sig: 'i', + emscripten_stack_get_current: function() { + return STACKTOP|0; + }, + emscripten_stack_get_free__asm: true, + emscripten_stack_get_free__sig: 'i', + emscripten_stack_get_free: function() { + return (STACK_MAX|0) - (STACKTOP|0); + } #endif }); diff --git a/system/include/emscripten/stack.h b/system/include/emscripten/stack.h new file mode 100644 index 000000000000..ea0972f367c0 --- /dev/null +++ b/system/include/emscripten/stack.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#pragma once + +// API that gives access to introspecting the Wasm data stack. Build with +// -lstack.js to use this API. + +#ifdef __cplusplus +extern "C" { +#endif + +// Returns the starting address of the wasm stack. This is the address +// that the stack pointer would point to when no bytes are in use on the stack. +uintptr_t emscripten_stack_get_base(void); + +// Returns the end address of the wasm stack. This is the address that the stack +// pointer would point to when the whole stack is in use. (the address pointed +// to by the end is not part of the stack itself) Note that in fastcomp, the +// stack grows up, whereas in wasm backend, it grows down. So with wasm +// backend, the address returned by emscripten_stack_get_end() is smaller than +// emscripten_stack_get_base(). +uintptr_t emscripten_stack_get_end(void); + +// Returns the current stack pointer. +uintptr_t emscripten_stack_get_current(void); + +// Returns the number of free bytes left on the stack. +size_t emscripten_stack_get_free(void); + +#ifdef __cplusplus +} +#endif diff --git a/system/lib/compiler-rt/stack_ops.s b/system/lib/compiler-rt/stack_ops.s index 15d07e4a3c6a..0e781d14d7ee 100644 --- a/system/lib/compiler-rt/stack_ops.s +++ b/system/lib/compiler-rt/stack_ops.s @@ -1,6 +1,8 @@ .globl stackSave .globl stackRestore .globl stackAlloc +.globl emscripten_stack_get_current +.globl emscripten_stack_get_free .globaltype __stack_pointer, i32 @@ -30,3 +32,27 @@ stackAlloc: global.set __stack_pointer local.get 1 end_function + +emscripten_stack_get_current: + .functype emscripten_stack_get_current () -> (i32) + 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_get_free: + # set __stack_base/__stack_end on first call + .functype emscripten_stack_get_free () -> (i32) + global.get __stack_end + i32.eqz + if + call emscripten_stack_get_end + global.set __stack_end + end_if + global.get __stack_pointer + global.get __stack_end + i32.sub + end_function diff --git a/tests/core/test_stack_get_free.c b/tests/core/test_stack_get_free.c new file mode 100644 index 000000000000..82d8ef3f1bd2 --- /dev/null +++ b/tests/core/test_stack_get_free.c @@ -0,0 +1,47 @@ +#include +#include +#include +#include +#include +#include +#include + +void __attribute__((noinline)) DoSomething(char *addr) { + memset(addr, 42, 13); +} + +void TestStackValidity() { + uintptr_t base = emscripten_stack_get_base(); + uintptr_t end = emscripten_stack_get_end(); + + uintptr_t used = abs((intptr_t)base - (intptr_t)emscripten_stack_get_current()); + uintptr_t free = abs((intptr_t)end - (intptr_t)emscripten_stack_get_current()); + uintptr_t free2 = emscripten_stack_get_free(); + uintptr_t total = abs((intptr_t)end - (intptr_t)base); + assert(used + free == total); + assert(free == free2); +} + +int increment = 256 * 1024; + +int main() { + TestStackValidity(); + + uintptr_t origFree = emscripten_stack_get_free(); + uintptr_t prevFree = emscripten_stack_get_free(); + printf("Stack used: %u\n", origFree - emscripten_stack_get_free()); + for(int i = 0; i < 10; ++i) { + int increment_noopt = emscripten_random() >= 0 ? increment : 2; + char *p = alloca(increment_noopt); + DoSomething(p); + uintptr_t free = emscripten_stack_get_free(); + assert(prevFree - free == increment); + prevFree = free; + // Print something from the allocationed region to prevent whole program + // optimizations from elminiating the alloca completely. + printf("Val: %d\n", p[10]); + printf("Stack used: %u\n", origFree - emscripten_stack_get_free()); + TestStackValidity(); + } + return 0; +} diff --git a/tests/core/test_stack_get_free.out b/tests/core/test_stack_get_free.out new file mode 100644 index 000000000000..c92f173d980f --- /dev/null +++ b/tests/core/test_stack_get_free.out @@ -0,0 +1,21 @@ +Stack used: 0 +Val: 42 +Stack used: 262144 +Val: 42 +Stack used: 524288 +Val: 42 +Stack used: 786432 +Val: 42 +Stack used: 1048576 +Val: 42 +Stack used: 1310720 +Val: 42 +Stack used: 1572864 +Val: 42 +Stack used: 1835008 +Val: 42 +Stack used: 2097152 +Val: 42 +Stack used: 2359296 +Val: 42 +Stack used: 2621440 diff --git a/tests/test_core.py b/tests/test_core.py index 0ea7c6c041fa..2344745286ee 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -8664,13 +8664,20 @@ def test_emscripten_math(self): self.do_run_in_out_file_test('tests', 'core', 'test_emscripten_math') # Tests that users can pass custom JS options from command line using - # the -jsDfoo=val syntax. (https://github.com/emscripten-core/emscripten/issues/10580) + # the -jsDfoo=val syntax: + # See https://github.com/emscripten-core/emscripten/issues/10580. def test_custom_js_options(self): self.emcc_args += ['--js-library', path_from_root('tests', 'core', 'test_custom_js_settings.js'), '-jsDCUSTOM_JS_OPTION=1'] self.do_run_in_out_file_test('tests', 'core', 'test_custom_js_settings') self.assertContained('cannot change built-in settings values with a -jsD directive', self.expect_fail([EMCC, '-jsDWASM=0'])) + # Tests API + def test_emscripten_stack(self): + self.emcc_args += ['-lstack.js'] + self.set_setting('TOTAL_STACK', 4 * 1024 * 1024) + self.do_run_in_out_file_test('tests', 'core', 'test_stack_get_free') + # Generate tests for everything def make_run(name, emcc_args, settings=None, env=None):