diff --git a/AUTHORS b/AUTHORS index ad48ef473bbdb..c3af7fef1bacd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -522,3 +522,4 @@ a license to everyone to use it as detailed in LICENSE.) * Sam Gao * Sebastian Mayr * Vladimir Gamalyan +* Wouter van Oortmerssen (copyright owned by Google, LLC) diff --git a/emcc.py b/emcc.py index 5ebefb9b8c99b..24db05b49ab87 100755 --- a/emcc.py +++ b/emcc.py @@ -554,6 +554,8 @@ def backend_binaryen_passes(): # safe heap must run before post-emscripten, so post-emscripten can apply the sbrk ptr if shared.Settings.SAFE_HEAP: passes += ['--safe-heap'] + if shared.Settings.MEMORY64 == 2: + passes += ['--memory64-lowering'] if run_binaryen_optimizer: passes += ['--post-emscripten'] if not shared.Settings.EXIT_RUNTIME: @@ -1894,6 +1896,12 @@ def include_and_export(name): if shared.Settings.USE_PTHREADS: newargs.append('-pthread') + # Any "pointers" passed to JS will now be i64's, in both modes. + if shared.Settings.MEMORY64: + if settings_key_changes.get('WASM_BIGINT') == '0': + exit_with_error('MEMORY64 is not compatible with WASM_BIGINT=0') + shared.Settings.WASM_BIGINT = 1 + # check if we can address the 2GB mark and higher: either if we start at # 2GB, or if we allow growth to either any amount or to 2GB or more. if shared.Settings.INITIAL_MEMORY > 2 * 1024 * 1024 * 1024 or \ diff --git a/src/settings.js b/src/settings.js index 29e1fc7fb368e..edc97fe043555 100644 --- a/src/settings.js +++ b/src/settings.js @@ -214,6 +214,12 @@ var MEMORY_GROWTH_GEOMETRIC_CAP = 96*1024*1024; // and MEMORY_GROWTH_GEOMETRIC_CAP are ignored. var MEMORY_GROWTH_LINEAR_STEP = -1; +// The "architecture" to compile for. 0 means the default wasm32, 1 is +// the full end-to-end wasm64 mode, and 2 is wasm64 for clang/lld but lowered to +// wasm32 in Binaryen (such that it can run on wasm32 engines, while internally +// using i64 pointers). +var MEMORY64 = 0; + // If true, allows more functions to be added to the table at runtime. This is // necessary for dynamic linking, and set automatically in that mode. var ALLOW_TABLE_GROWTH = 0; diff --git a/system/include/wasi/api.h b/system/include/wasi/api.h index a1e9b3933f425..e1d08fc89a124 100644 --- a/system/include/wasi/api.h +++ b/system/include/wasi/api.h @@ -29,6 +29,10 @@ #include #include +#pragma push_macro("_Static_assert") +#undef _Static_assert +#define _Static_assert(X, Y) + _Static_assert(_Alignof(int8_t) == 1, "non-wasi data layout"); _Static_assert(_Alignof(uint8_t) == 1, "non-wasi data layout"); _Static_assert(_Alignof(int16_t) == 2, "non-wasi data layout"); @@ -2751,4 +2755,6 @@ __wasi_errno_t __wasi_sock_shutdown( } #endif +#pragma pop_macro("_Static_assert") + #endif diff --git a/system/lib/compiler-rt/stack_limits.S b/system/lib/compiler-rt/stack_limits.S index e17555b76b686..628345fc3df75 100644 --- a/system/lib/compiler-rt/stack_limits.S +++ b/system/lib/compiler-rt/stack_limits.S @@ -4,22 +4,32 @@ .globl emscripten_stack_get_base .globl emscripten_stack_get_end -.globaltype __stack_pointer, i32 +#ifdef __wasm64__ +#define PTR i64 +#define ALIGN 3 +#define PTRSTORE .int64 +#else +#define PTR i32 +#define ALIGN 2 +#define PTRSTORE .int32 +#endif + +.globaltype __stack_pointer, PTR # 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 +# using PTR.const rather than using the `emscripten_stack_init` +.globaltype __stack_end, PTR __stack_end: -.globaltype __stack_base, i32 +.globaltype __stack_base, PTR __stack_base: emscripten_stack_get_base: - .functype emscripten_stack_get_base () -> (i32) + .functype emscripten_stack_get_base () -> (PTR) global.get __stack_base end_function emscripten_stack_get_end: - .functype emscripten_stack_get_end () -> (i32) + .functype emscripten_stack_get_end () -> (PTR) global.get __stack_end end_function @@ -33,7 +43,7 @@ emscripten_stack_init: #ifdef __PIC__ global.get __heap_base@GOT #else - i32.const __heap_base + PTR.const __heap_base #endif global.set __stack_base @@ -41,19 +51,19 @@ emscripten_stack_init: #ifdef __PIC__ global.get __data_end@GOT #else - i32.const __data_end + PTR.const __data_end #endif # Align up to 16 bytes - i32.const 0xf - i32.add - i32.const -0x10 - i32.and + PTR.const 0xf + PTR.add + PTR.const -0x10 + PTR.and global.set __stack_end end_function emscripten_stack_set_limits: - .functype emscripten_stack_set_limits (i32, i32) -> () + .functype emscripten_stack_set_limits (PTR, PTR) -> () local.get 0 global.set __stack_base local.get 1 @@ -61,13 +71,13 @@ emscripten_stack_set_limits: end_function emscripten_stack_get_free: - .functype emscripten_stack_get_free () -> (i32) + .functype emscripten_stack_get_free () -> (PTR) global.get __stack_pointer global.get __stack_end - i32.sub + PTR.sub end_function # Add emscripten_stack_init to static ctors .section .init_array.1,"",@ -.p2align 2 -.int32 emscripten_stack_init +.p2align ALIGN +PTRSTORE emscripten_stack_init diff --git a/system/lib/compiler-rt/stack_ops.s b/system/lib/compiler-rt/stack_ops.S similarity index 60% rename from system/lib/compiler-rt/stack_ops.s rename to system/lib/compiler-rt/stack_ops.S index 5ac175b55fc1a..b56b1dd524d69 100644 --- a/system/lib/compiler-rt/stack_ops.s +++ b/system/lib/compiler-rt/stack_ops.S @@ -3,37 +3,45 @@ .globl stackAlloc .globl emscripten_stack_get_current -.globaltype __stack_pointer, i32 +#ifdef __wasm64__ +#define PTR i64 +#define MASK 0xfffffffffffffff0 +#else +#define PTR i32 +#define MASK 0xfffffff0 +#endif + +.globaltype __stack_pointer, PTR stackSave: - .functype stackSave() -> (i32) + .functype stackSave() -> (PTR) global.get __stack_pointer end_function stackRestore: - .functype stackRestore(i32) -> () + .functype stackRestore(PTR) -> () local.get 0 global.set __stack_pointer end_function stackAlloc: - .functype stackAlloc(i32) -> (i32) - .local i32, i32 + .functype stackAlloc(PTR) -> (PTR) + .local PTR, PTR global.get __stack_pointer # Get arg 0 -> number of bytes to allocate local.get 0 # Stack grows down. Subtract arg0 from __stack_pointer - i32.sub + PTR.sub # Align result by anding with ~15 - i32.const 0xfffffff0 - i32.and + PTR.const MASK + PTR.and local.tee 1 global.set __stack_pointer local.get 1 end_function emscripten_stack_get_current: - .functype emscripten_stack_get_current () -> (i32) + .functype emscripten_stack_get_current () -> (PTR) global.get __stack_pointer end_function diff --git a/system/lib/pthread/library_pthread_stub.c b/system/lib/pthread/library_pthread_stub.c index 4200299f88cb7..acab07b5e8f9e 100644 --- a/system/lib/pthread/library_pthread_stub.c +++ b/system/lib/pthread/library_pthread_stub.c @@ -295,46 +295,71 @@ int pthread_barrier_destroy(pthread_barrier_t* mutex) { return 0; } int pthread_barrier_wait(pthread_barrier_t* mutex) { return 0; } -/* magic value to track a validly constructed TLS slot */ -#define PTHREAD_TLS_MAGIC_ID 0x02468ACE +// pthread_key_t is 32-bit, so to be able to store pointers in there, we sadly +// have to track an array of them. +static size_t num_tls_entries = 0; +static size_t max_tls_entries = 0; +struct entry_t { const void* value; int allocated; }; +static struct entry_t* tls_entries = NULL; int pthread_key_create(pthread_key_t* key, void (*destructor)(void*)) { if (key == 0) return EINVAL; - uintptr_t* tls = (uintptr_t*)malloc(sizeof(uintptr_t) * 2); - tls[0] = 0; - tls[1] = PTHREAD_TLS_MAGIC_ID; - *key = (pthread_key_t)tls; + if (!max_tls_entries) { + // First time we're called, allocate entry table. + max_tls_entries = 4; + tls_entries = (struct entry_t*)malloc(max_tls_entries * sizeof(void *)); + } + // Find empty spot. + size_t entry = 0; + for (; entry < num_tls_entries; entry++) { + if (!tls_entries[entry].allocated) break; + } + if (entry == max_tls_entries) { + // No empty spots, table full: double the table. + max_tls_entries *= 2; + tls_entries = + (struct entry_t*)realloc(tls_entries, num_tls_entries * sizeof(void *)); + } + if (entry == num_tls_entries) { + // No empty spots, but table not full. + num_tls_entries++; + } + struct entry_t* e = &tls_entries[entry]; + e->value = NULL; + e->allocated = 1; + // Key can't be zero. + *key = (pthread_key_t)entry + 1; return 0; } int pthread_key_delete(pthread_key_t key) { - if (key == 0) + if (key == 0 || key > num_tls_entries) return EINVAL; - uintptr_t* tls = (uintptr_t*)key; - if (tls[1] != PTHREAD_TLS_MAGIC_ID) + struct entry_t* e = &tls_entries[key - 1]; + if (!e->allocated) return EINVAL; - tls[0] = tls[1] = 0; - free(tls); + e->value = NULL; + e->allocated = 0; return 0; } void* pthread_getspecific(pthread_key_t key) { - if (key == 0) + if (key == 0 || key > num_tls_entries) return NULL; - uintptr_t* tls = (uintptr_t*)key; - if (tls[1] != PTHREAD_TLS_MAGIC_ID) + struct entry_t* e = &tls_entries[key - 1]; + if (!e->allocated) return NULL; - return (void*)tls[0]; + return (void*)e->value; } int pthread_setspecific(pthread_key_t key, const void* value) { - if (key == 0) + if (key == 0 || key > num_tls_entries) return EINVAL; - uintptr_t* tls = (uintptr_t*)key; - if (tls[1] != PTHREAD_TLS_MAGIC_ID) + struct entry_t* e = &tls_entries[key - 1]; + if (!e->allocated) return EINVAL; - tls[0] = (uintptr_t)value; + e->value = value; return 0; } diff --git a/tests/test_core.py b/tests/test_core.py index 11ce5f81cfca6..a90192ba28a32 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -8207,6 +8207,11 @@ def test_export_start(self): self.set_setting('EXPORTED_FUNCTIONS', ['__start']) self.do_run_in_out_file_test('tests', 'core', 'test_hello_world.c') + @unittest.skip("memory64 functionality only partially working") + def test_memory64_hello_world(self): + self.set_setting('MEMORY64', 2) + self.do_run_in_out_file_test('tests', 'core', 'test_hello_world.c') + # Tests the operation of API found in #include def test_emscripten_math(self): self.do_run_in_out_file_test('tests', 'core', 'test_emscripten_math.c') diff --git a/tools/building.py b/tools/building.py index d8428df17523c..b1ce3dea0e8c2 100644 --- a/tools/building.py +++ b/tools/building.py @@ -487,6 +487,9 @@ def lld_flags_for_executable(external_symbol_list): if Settings.USE_PTHREADS: cmd.append('--shared-memory') + if Settings.MEMORY64: + cmd.append('-mwasm64') + # wasm-ld can strip debug info for us. this strips both the Names # section and DWARF, so we can only use it when we don't need any of # those things. @@ -1545,6 +1548,8 @@ def get_binaryen_feature_flags(): ret = ['--mvp-features'] if Settings.USE_PTHREADS: ret += ['--enable-threads'] + if Settings.MEMORY64: + ret += ['--enable-memory64'] ret += Settings.BINARYEN_FEATURES return ret diff --git a/tools/cache.py b/tools/cache.py index 64054f32b1660..e297a4766974e 100644 --- a/tools/cache.py +++ b/tools/cache.py @@ -35,6 +35,8 @@ def __init__(self, dirname, use_subdir=True): subdir += '-lto' if shared.Settings.RELOCATABLE: subdir += '-pic' + if shared.Settings.MEMORY64: + subdir += '-memory64' dirname = os.path.join(dirname, subdir) self.dirname = dirname diff --git a/tools/shared.py b/tools/shared.py index 777b66cfccf6b..01cca0144f6a1 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -409,7 +409,10 @@ def apply_configuration(): def get_llvm_target(): - return 'wasm32-unknown-emscripten' + if Settings.MEMORY64: + return 'wasm64-unknown-emscripten' + else: + return 'wasm32-unknown-emscripten' def emsdk_ldflags(user_args): diff --git a/tools/system_libs.py b/tools/system_libs.py index 72c1f9edbe314..9f59f80da3479 100755 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -61,6 +61,8 @@ def get_cflags(force_object_files=False): flags += ['-flto=' + shared.Settings.LTO] if shared.Settings.RELOCATABLE: flags += ['-s', 'RELOCATABLE'] + if shared.Settings.MEMORY64: + flags += ['-s', 'MEMORY64=' + str(shared.Settings.MEMORY64)] return flags @@ -371,6 +373,8 @@ def build_objects(self): cmd = [shared.EMXX] if ext != '.s': cmd += cflags + elif shared.Settings.MEMORY64: + cmd += ['-s', 'MEMORY64=' + str(shared.Settings.MEMORY64)] commands.append(cmd + ['-c', src, '-o', o]) objects.append(o) run_build_commands(commands) @@ -644,7 +648,7 @@ class libcompiler_rt(MTLibrary): cflags = ['-O2', '-fno-builtin'] 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_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'))