From c2272d0ebb0e5b22ac51ddb18173d6118055ab3e Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Fri, 21 Aug 2026 14:08:44 -0700 Subject: [PATCH 1/4] Add test for EMSCRIPTEN_KEEPALIVE behavior Check that EMSCRIPTEN_KEEPALIVE retains functions and global variables, and exports them under their linkage name. --- test/test_other.py | 71 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/test/test_other.py b/test/test_other.py index ac25238850111..5cb7d3be95817 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -16107,3 +16107,74 @@ def test_download_failure(self): EXTERNAL_PORT = URL ''') self.assert_fail([EMCC, test_file('hello_world.c'), f'--use-port={bad_port_path}'], 'failed to download port "bad_port"') + + @parameterized({ + '': ([],), + 'O1': (['-O1'],), + 'O2': (['-O2'],), + 'O3': (['-O3', '-g'],), + 'lto': (['-flto'],), + }) + def test_emscripten_keepalive(self, args): + create_file('main.c', r''' +#include +#include + +EMSCRIPTEN_KEEPALIVE +int my_kept_global = 42; + +EMSCRIPTEN_KEEPALIVE +int my_kept_func(int x) { + return x + my_kept_global; +} + +int main() { + printf("kept func: %d\n", my_kept_func(10)); + return 0; +} +''') + self.run_process([EMCC, 'main.c', '-o', 'main.html'] + args) + self.assertTrue(self.is_exported_in_wasm('my_kept_func', 'main.wasm')) + self.assertTrue(self.is_exported_in_wasm('my_kept_global', 'main.wasm')) + output = self.run_js('main.js') + self.assertContained('kept func: 52', output) + + @parameterized({ + '': ([],), + 'O2': (['-O2'],), + }) + def test_emscripten_keepalive_cpp(self, args): + create_file('cpp_main.cpp', r''' +#include +#include + +namespace my_ns { +EMSCRIPTEN_KEEPALIVE int ns_global = 123; +} + +EMSCRIPTEN_KEEPALIVE int overloaded_func(int x) { return x + 1; } +EMSCRIPTEN_KEEPALIVE int overloaded_func(double x) { return (int)x + 2; } + +extern "C" { +EMSCRIPTEN_KEEPALIVE +int cpp_kept_global = 100; + +EMSCRIPTEN_KEEPALIVE +int cpp_kept_func(int y) { + return y * 2 + cpp_kept_global; +} +} + +int main() { + std::cout << "cpp kept func: " << cpp_kept_func(5) << ' ' << overloaded_func(10) << ' ' << overloaded_func(20.0) << std::endl; + return 0; +} +''') + self.run_process([EMXX, 'cpp_main.cpp', '-o', 'cpp_main.html'] + args) + self.assertTrue(self.is_exported_in_wasm('cpp_kept_func', 'cpp_main.wasm')) + self.assertTrue(self.is_exported_in_wasm('cpp_kept_global', 'cpp_main.wasm')) + self.assertTrue(self.is_exported_in_wasm('_Z15overloaded_funci', 'cpp_main.wasm')) + self.assertTrue(self.is_exported_in_wasm('_Z15overloaded_funcd', 'cpp_main.wasm')) + self.assertTrue(self.is_exported_in_wasm('_ZN5my_ns9ns_globalE', 'cpp_main.wasm')) + output = self.run_js('cpp_main.js') + self.assertContained('cpp kept func: 110 11 22', output) From 13701923722ffa684de2739424856ec8fc720900 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Fri, 21 Aug 2026 14:10:42 -0700 Subject: [PATCH 2/4] no need to run --- test/test_other.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/test_other.py b/test/test_other.py index 5cb7d3be95817..c96f7ba17d8d6 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -16136,8 +16136,6 @@ def test_emscripten_keepalive(self, args): self.run_process([EMCC, 'main.c', '-o', 'main.html'] + args) self.assertTrue(self.is_exported_in_wasm('my_kept_func', 'main.wasm')) self.assertTrue(self.is_exported_in_wasm('my_kept_global', 'main.wasm')) - output = self.run_js('main.js') - self.assertContained('kept func: 52', output) @parameterized({ '': ([],), @@ -16176,5 +16174,3 @@ def test_emscripten_keepalive_cpp(self, args): self.assertTrue(self.is_exported_in_wasm('_Z15overloaded_funci', 'cpp_main.wasm')) self.assertTrue(self.is_exported_in_wasm('_Z15overloaded_funcd', 'cpp_main.wasm')) self.assertTrue(self.is_exported_in_wasm('_ZN5my_ns9ns_globalE', 'cpp_main.wasm')) - output = self.run_js('cpp_main.js') - self.assertContained('cpp kept func: 110 11 22', output) From 9738e612241fbcd87c67009f35de0dc286aa2a3c Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Fri, 21 Aug 2026 15:08:12 -0700 Subject: [PATCH 3/4] Use existing test --- system/include/emscripten/em_macros.h | 2 +- test/other/test_export_global_address.c | 29 --------- test/other/test_export_global_address.cpp | 77 +++++++++++++++++++++++ test/other/test_export_global_address.out | 8 ++- test/test_other.py | 72 +-------------------- 5 files changed, 87 insertions(+), 101 deletions(-) delete mode 100644 test/other/test_export_global_address.c create mode 100644 test/other/test_export_global_address.cpp diff --git a/system/include/emscripten/em_macros.h b/system/include/emscripten/em_macros.h index 3cf789d4138ca..e0e1e856e49be 100644 --- a/system/include/emscripten/em_macros.h +++ b/system/include/emscripten/em_macros.h @@ -7,7 +7,7 @@ #pragma once -#define EMSCRIPTEN_KEEPALIVE __attribute__((used)) +#define EMSCRIPTEN_KEEPALIVE __attribute__((export_name)) #ifdef __wasm__ #define EM_IMPORT(NAME) __attribute__((import_module("env"), import_name(#NAME))) diff --git a/test/other/test_export_global_address.c b/test/other/test_export_global_address.c deleted file mode 100644 index e815a74bda74a..0000000000000 --- a/test/other/test_export_global_address.c +++ /dev/null @@ -1,29 +0,0 @@ -#include -#include -#include - -#ifdef USE_KEEPALIVE -EMSCRIPTEN_KEEPALIVE -#endif -int g_foo = 4; - -EM_JS(int*, get_foo_from_js, (void), { - assert(_g_foo !== undefined, "g_foo not exported to JS"); -#if __wasm64__ - return BigInt(_g_foo); -#else - return _g_foo; -#endif -}); - -int main() { - printf("get_foo_from_js: %d\n", *get_foo_from_js()); - printf("g_foo: %d\n", g_foo); - if (get_foo_from_js() != &g_foo) { - printf("addresses failed to match\n"); - printf("js: %p\n", get_foo_from_js()); - printf("native: %p\n", &g_foo); - return 1; - } - return 0; -} diff --git a/test/other/test_export_global_address.cpp b/test/other/test_export_global_address.cpp new file mode 100644 index 0000000000000..fd4348b8b6e17 --- /dev/null +++ b/test/other/test_export_global_address.cpp @@ -0,0 +1,77 @@ +#include +#include +#include + +#ifdef USE_KEEPALIVE +#define KEEPALIVE EMSCRIPTEN_KEEPALIVE +#else +#define KEEPALIVE +#endif + +extern "C" { +KEEPALIVE int g_var = 4; + +KEEPALIVE int g_func(int x) { + return x + g_var; +} +} + +namespace ns { +KEEPALIVE int ns_var = 42; +} + +KEEPALIVE int cpp_func(int x) { + return x * 2; +} + +EM_JS(int*, get_var_from_js, (void), { + assert(_g_var !== undefined, "g_var not exported to JS"); +#if __wasm64__ + return BigInt(_g_var); +#else + return _g_var; +#endif +}); + +EM_JS(int, call_func_from_js, (int arg), { + assert(_g_func !== undefined, "g_func not exported to JS"); + return _g_func(arg); +}); + +EM_JS(int*, get_ns_var_from_js, (void), { + assert(__ZN2ns6ns_varE !== undefined, "ns::ns_var not exported to JS"); +#if __wasm64__ + return BigInt(__ZN2ns6ns_varE); +#else + return __ZN2ns6ns_varE; +#endif +}); + +EM_JS(int, call_cpp_func_from_js, (int arg), { + assert(__Z8cpp_funci !== undefined, "cpp_func not exported to JS"); + return __Z8cpp_funci(arg); +}); + +int main() { + printf("get_var_from_js: %d\n", *get_var_from_js()); + printf("g_var: %d\n", g_var); + if (get_var_from_js() != &g_var) { + printf("addresses failed to match\n"); + printf("js: %p\n", get_var_from_js()); + printf("native: %p\n", &g_var); + return 1; + } + printf("call_func_from_js: %d\n", call_func_from_js(10)); + + printf("get_ns_var_from_js: %d\n", *get_ns_var_from_js()); + printf("ns_var: %d\n", ns::ns_var); + if (get_ns_var_from_js() != &ns::ns_var) { + printf("ns_var addresses failed to match\n"); + printf("js: %p\n", get_ns_var_from_js()); + printf("native: %p\n", &ns::ns_var); + return 1; + } + printf("call_cpp_func_from_js: %d\n", call_cpp_func_from_js(5)); + + return 0; +} diff --git a/test/other/test_export_global_address.out b/test/other/test_export_global_address.out index b6338edf0388e..c99f3e3ec4076 100644 --- a/test/other/test_export_global_address.out +++ b/test/other/test_export_global_address.out @@ -1,2 +1,6 @@ -get_foo_from_js: 4 -g_foo: 4 +get_var_from_js: 4 +g_var: 4 +call_func_from_js: 14 +get_ns_var_from_js: 42 +ns_var: 42 +call_cpp_func_from_js: 10 diff --git a/test/test_other.py b/test/test_other.py index c96f7ba17d8d6..58acb021a15fb 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -12350,11 +12350,11 @@ def test_assembly_preprocessed(self): @parameterized({ '': (['-DUSE_KEEPALIVE'],), 'minimal': (['-DUSE_KEEPALIVE', '-sMINIMAL_RUNTIME'],), - 'command_line': (['-sEXPORTED_FUNCTIONS=_g_foo,_main'],), - 'himem': (['-sEXPORTED_FUNCTIONS=_g_foo,_main', '-sGLOBAL_BASE=2gb', '-sINITIAL_MEMORY=3gb'],), + 'command_line': (['-sEXPORTED_FUNCTIONS=_g_var,_g_func,__ZN2ns6ns_varE,__Z8cpp_funci,_main'],), + 'himem': (['-sEXPORTED_FUNCTIONS=_g_var,_g_func,__ZN2ns6ns_varE,__Z8cpp_funci,_main', '-sGLOBAL_BASE=2gb', '-sINITIAL_MEMORY=3gb'],), }) def test_export_global_address(self, args): - self.do_other_test('test_export_global_address.c', cflags=args) + self.do_other_test('test_export_global_address.cpp', cflags=args) def test_linker_version(self): out = self.run_process([EMCC, '-Wl,--version'], stdout=PIPE).stdout @@ -16108,69 +16108,3 @@ def test_download_failure(self): ''') self.assert_fail([EMCC, test_file('hello_world.c'), f'--use-port={bad_port_path}'], 'failed to download port "bad_port"') - @parameterized({ - '': ([],), - 'O1': (['-O1'],), - 'O2': (['-O2'],), - 'O3': (['-O3', '-g'],), - 'lto': (['-flto'],), - }) - def test_emscripten_keepalive(self, args): - create_file('main.c', r''' -#include -#include - -EMSCRIPTEN_KEEPALIVE -int my_kept_global = 42; - -EMSCRIPTEN_KEEPALIVE -int my_kept_func(int x) { - return x + my_kept_global; -} - -int main() { - printf("kept func: %d\n", my_kept_func(10)); - return 0; -} -''') - self.run_process([EMCC, 'main.c', '-o', 'main.html'] + args) - self.assertTrue(self.is_exported_in_wasm('my_kept_func', 'main.wasm')) - self.assertTrue(self.is_exported_in_wasm('my_kept_global', 'main.wasm')) - - @parameterized({ - '': ([],), - 'O2': (['-O2'],), - }) - def test_emscripten_keepalive_cpp(self, args): - create_file('cpp_main.cpp', r''' -#include -#include - -namespace my_ns { -EMSCRIPTEN_KEEPALIVE int ns_global = 123; -} - -EMSCRIPTEN_KEEPALIVE int overloaded_func(int x) { return x + 1; } -EMSCRIPTEN_KEEPALIVE int overloaded_func(double x) { return (int)x + 2; } - -extern "C" { -EMSCRIPTEN_KEEPALIVE -int cpp_kept_global = 100; - -EMSCRIPTEN_KEEPALIVE -int cpp_kept_func(int y) { - return y * 2 + cpp_kept_global; -} -} - -int main() { - std::cout << "cpp kept func: " << cpp_kept_func(5) << ' ' << overloaded_func(10) << ' ' << overloaded_func(20.0) << std::endl; - return 0; -} -''') - self.run_process([EMXX, 'cpp_main.cpp', '-o', 'cpp_main.html'] + args) - self.assertTrue(self.is_exported_in_wasm('cpp_kept_func', 'cpp_main.wasm')) - self.assertTrue(self.is_exported_in_wasm('cpp_kept_global', 'cpp_main.wasm')) - self.assertTrue(self.is_exported_in_wasm('_Z15overloaded_funci', 'cpp_main.wasm')) - self.assertTrue(self.is_exported_in_wasm('_Z15overloaded_funcd', 'cpp_main.wasm')) - self.assertTrue(self.is_exported_in_wasm('_ZN5my_ns9ns_globalE', 'cpp_main.wasm')) From 69dc4f634698296b59cc68eac2f51716c1ec6d86 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Fri, 21 Aug 2026 15:15:20 -0700 Subject: [PATCH 4/4] fix ruff, remove implementation change --- system/include/emscripten/em_macros.h | 2 +- test/test_other.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/system/include/emscripten/em_macros.h b/system/include/emscripten/em_macros.h index e0e1e856e49be..3cf789d4138ca 100644 --- a/system/include/emscripten/em_macros.h +++ b/system/include/emscripten/em_macros.h @@ -7,7 +7,7 @@ #pragma once -#define EMSCRIPTEN_KEEPALIVE __attribute__((export_name)) +#define EMSCRIPTEN_KEEPALIVE __attribute__((used)) #ifdef __wasm__ #define EM_IMPORT(NAME) __attribute__((import_module("env"), import_name(#NAME))) diff --git a/test/test_other.py b/test/test_other.py index 58acb021a15fb..d1a48e3ab74c0 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -16107,4 +16107,3 @@ def test_download_failure(self): EXTERNAL_PORT = URL ''') self.assert_fail([EMCC, test_file('hello_world.c'), f'--use-port={bad_port_path}'], 'failed to download port "bad_port"') -