diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e334484f..8fa7302ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,6 +204,17 @@ add_link_options( -resource-dir ${tmp_resource_dir} ) +if (HAVE_WASM_LIBCALL_THREAD_CONTEXT) + # TODO: shouldn't have to pass these as it means it's required by all end + # users as well. Ideally this would be part of the driver in rustc/clang, but + # that's left for a future change. + add_link_options( + -Wl,--export-if-defined=__wasm_init_tls + -Wl,--export-if-defined=__tls_size + -Wl,--export-if-defined=__tls_align + ) +endif() + # Expose the public headers to the implementation. We use `-isystem` for # purpose for two reasons: # diff --git a/cmake/scripts/run-check-symbols.cmake b/cmake/scripts/run-check-symbols.cmake index ba77e8468..38cf2460b 100644 --- a/cmake/scripts/run-check-symbols.cmake +++ b/cmake/scripts/run-check-symbols.cmake @@ -98,7 +98,7 @@ foreach(symbol IN LISTS final_undefined_symbols) NOT symbol MATCHES "__stack_pointer" AND NOT symbol MATCHES "__init_stack_pointer" AND NOT symbol MATCHES "__wasm_component_model_builtin.*" AND - NOT symbol STREQUAL "__tls_base") + NOT symbol MATCHES "^__tls_(base|size|align)$") file(APPEND ${out_undefined_symbols} "${symbol}\n") endif() endforeach() diff --git a/expected/wasm32-wasip1-threads/include-all.c b/expected/wasm32-wasip1-threads/include-all.c index ad12f0cac..8c6422925 100644 --- a/expected/wasm32-wasip1-threads/include-all.c +++ b/expected/wasm32-wasip1-threads/include-all.c @@ -1,4 +1,3 @@ -#include <__errno.h> #include <__errno_values.h> #include <__fd_set.h> #include <__function___isatty.h> diff --git a/expected/wasm32-wasip1-threads/predefined-macros.txt b/expected/wasm32-wasip1-threads/predefined-macros.txt index 1b448aeb9..2f9ca723c 100644 --- a/expected/wasm32-wasip1-threads/predefined-macros.txt +++ b/expected/wasm32-wasip1-threads/predefined-macros.txt @@ -3105,7 +3105,6 @@ #define __wasi_libc_h #define __wasi_libc_nocwd_h #define __wasi_wasip1_h -#define __wasilibc___errno_h #define __wasilibc___errno_values_h #define __wasilibc___fd_set_h #define __wasilibc___function___isatty_h diff --git a/expected/wasm32-wasip1-threads/undefined-symbols.txt b/expected/wasm32-wasip1-threads/undefined-symbols.txt index d612ed957..d058e90ac 100644 --- a/expected/wasm32-wasip1-threads/undefined-symbols.txt +++ b/expected/wasm32-wasip1-threads/undefined-symbols.txt @@ -64,8 +64,6 @@ __letf2 __lttf2 __netf2 __subtf3 -__tls_align -__tls_size __trunctfdf2 __trunctfsf2 __unordtf2 diff --git a/expected/wasm32-wasip1/include-all.c b/expected/wasm32-wasip1/include-all.c index ad12f0cac..8c6422925 100644 --- a/expected/wasm32-wasip1/include-all.c +++ b/expected/wasm32-wasip1/include-all.c @@ -1,4 +1,3 @@ -#include <__errno.h> #include <__errno_values.h> #include <__fd_set.h> #include <__function___isatty.h> diff --git a/expected/wasm32-wasip1/predefined-macros.txt b/expected/wasm32-wasip1/predefined-macros.txt index 16fdb3a68..99a4149a4 100644 --- a/expected/wasm32-wasip1/predefined-macros.txt +++ b/expected/wasm32-wasip1/predefined-macros.txt @@ -3104,7 +3104,6 @@ #define __wasi_libc_h #define __wasi_libc_nocwd_h #define __wasi_wasip1_h -#define __wasilibc___errno_h #define __wasilibc___errno_values_h #define __wasilibc___fd_set_h #define __wasilibc___function___isatty_h diff --git a/expected/wasm32-wasip2/include-all.c b/expected/wasm32-wasip2/include-all.c index 2675563e8..21b3d78ca 100644 --- a/expected/wasm32-wasip2/include-all.c +++ b/expected/wasm32-wasip2/include-all.c @@ -1,4 +1,3 @@ -#include <__errno.h> #include <__errno_values.h> #include <__fd_set.h> #include <__function___isatty.h> diff --git a/expected/wasm32-wasip2/predefined-macros.txt b/expected/wasm32-wasip2/predefined-macros.txt index 2c7d41b28..8713db27e 100644 --- a/expected/wasm32-wasip2/predefined-macros.txt +++ b/expected/wasm32-wasip2/predefined-macros.txt @@ -3102,7 +3102,6 @@ #define __wasi_libc_h #define __wasi_libc_nocwd_h #define __wasi_wasip1_h -#define __wasilibc___errno_h #define __wasilibc___errno_values_h #define __wasilibc___fd_set_h #define __wasilibc___function___isatty_h diff --git a/expected/wasm32-wasip3-coop/defined-symbols.txt b/expected/wasm32-wasip3-coop/defined-symbols.txt index 9f60fb500..9906719d0 100644 --- a/expected/wasm32-wasip3-coop/defined-symbols.txt +++ b/expected/wasm32-wasip3-coop/defined-symbols.txt @@ -90,6 +90,7 @@ __getdelim __getentropy __getopt_msg __gmtime_r +__h_errno_location __hashmap_bucket_item __hashmap_clear __hashmap_count @@ -352,7 +353,6 @@ __wasilibc_get_service_entry_by_name __wasilibc_get_service_entry_by_port __wasilibc_getsockopt_timeout __wasilibc_iftodt -__wasilibc_init_async_task __wasilibc_init_stdio __wasilibc_initialize_environ __wasilibc_link @@ -387,6 +387,7 @@ __wasilibc_rename_newat __wasilibc_rename_oldat __wasilibc_reset_preopens __wasilibc_rmdirat +__wasilibc_set_task_tls __wasilibc_setsockopt_timeout __wasilibc_sockaddr_to_wasi __wasilibc_sockaddr_validate diff --git a/expected/wasm32-wasip3-coop/include-all.c b/expected/wasm32-wasip3-coop/include-all.c index 770ce5d76..112d20de0 100644 --- a/expected/wasm32-wasip3-coop/include-all.c +++ b/expected/wasm32-wasip3-coop/include-all.c @@ -1,4 +1,3 @@ -#include <__errno.h> #include <__errno_values.h> #include <__fd_set.h> #include <__function___isatty.h> diff --git a/expected/wasm32-wasip3-coop/predefined-macros.txt b/expected/wasm32-wasip3-coop/predefined-macros.txt index 7c4078552..cac483c9e 100644 --- a/expected/wasm32-wasip3-coop/predefined-macros.txt +++ b/expected/wasm32-wasip3-coop/predefined-macros.txt @@ -3109,7 +3109,6 @@ #define __wasi_libc_h #define __wasi_libc_nocwd_h #define __wasi_wasip1_h -#define __wasilibc___errno_h #define __wasilibc___errno_values_h #define __wasilibc___fd_set_h #define __wasilibc___function___isatty_h @@ -3227,7 +3226,7 @@ #define dirent64 dirent #define erf(x) __tg_real(erf, (x)) #define erfc(x) __tg_real(erfc, (x)) -#define errno errno +#define errno (*__errno_location()) #define exp(x) __tg_real_complex(exp, (x)) #define exp2(x) __tg_real(exp2, (x)) #define expm1(x) __tg_real(expm1, (x)) @@ -3259,7 +3258,7 @@ #define glob64_t glob_t #define globfree64 globfree #define h_addr h_addr_list[0] -#define h_errno h_errno +#define h_errno (*__h_errno_location()) #define howmany(n,d) (((n)+((d)-1))/(d)) #define htobe16(x) __bswap16(x) #define htobe32(x) __bswap32(x) diff --git a/expected/wasm32-wasip3-coop/undefined-symbols.txt b/expected/wasm32-wasip3-coop/undefined-symbols.txt index be3b98155..a5fa00b44 100644 --- a/expected/wasm32-wasip3-coop/undefined-symbols.txt +++ b/expected/wasm32-wasip3-coop/undefined-symbols.txt @@ -27,8 +27,6 @@ __subtask_drop __subtf3 __task_cancel __thread_yield -__tls_align -__tls_size __trunctfdf2 __trunctfsf2 __unordtf2 @@ -215,6 +213,8 @@ stream_drop_writable_unit stream_new_unit stream_read_unit stream_write_unit +wasip3_context_get_1 +wasip3_context_set_1 wasip3_thread_index wasip3_thread_new_indirect wasip3_thread_resume_later diff --git a/expected/wasm32-wasip3/defined-symbols.txt b/expected/wasm32-wasip3/defined-symbols.txt index 12b6fd769..847206ab3 100644 --- a/expected/wasm32-wasip3/defined-symbols.txt +++ b/expected/wasm32-wasip3/defined-symbols.txt @@ -89,6 +89,7 @@ __getdelim __getentropy __getopt_msg __gmtime_r +__h_errno_location __hwcap __inet_aton __inhibit_ptc diff --git a/expected/wasm32-wasip3/include-all.c b/expected/wasm32-wasip3/include-all.c index 770ce5d76..112d20de0 100644 --- a/expected/wasm32-wasip3/include-all.c +++ b/expected/wasm32-wasip3/include-all.c @@ -1,4 +1,3 @@ -#include <__errno.h> #include <__errno_values.h> #include <__fd_set.h> #include <__function___isatty.h> diff --git a/expected/wasm32-wasip3/predefined-macros.txt b/expected/wasm32-wasip3/predefined-macros.txt index b8b86c434..6ec84bc56 100644 --- a/expected/wasm32-wasip3/predefined-macros.txt +++ b/expected/wasm32-wasip3/predefined-macros.txt @@ -3108,7 +3108,6 @@ #define __wasi_libc_h #define __wasi_libc_nocwd_h #define __wasi_wasip1_h -#define __wasilibc___errno_h #define __wasilibc___errno_values_h #define __wasilibc___fd_set_h #define __wasilibc___function___isatty_h @@ -3226,7 +3225,7 @@ #define dirent64 dirent #define erf(x) __tg_real(erf, (x)) #define erfc(x) __tg_real(erfc, (x)) -#define errno errno +#define errno (*__errno_location()) #define exp(x) __tg_real_complex(exp, (x)) #define exp2(x) __tg_real(exp2, (x)) #define expm1(x) __tg_real(expm1, (x)) @@ -3258,7 +3257,7 @@ #define glob64_t glob_t #define globfree64 globfree #define h_addr h_addr_list[0] -#define h_errno h_errno +#define h_errno (*__h_errno_location()) #define howmany(n,d) (((n)+((d)-1))/(d)) #define htobe16(x) __bswap16(x) #define htobe32(x) __bswap32(x) diff --git a/libc-bottom-half/CMakeLists.txt b/libc-bottom-half/CMakeLists.txt index 8ed306b75..13b68df8e 100644 --- a/libc-bottom-half/CMakeLists.txt +++ b/libc-bottom-half/CMakeLists.txt @@ -185,7 +185,7 @@ endif() if (HAVE_WASM_LIBCALL_THREAD_CONTEXT) list(APPEND bottom_half_sources - sources/wasip3_non_coop_tls_base.S + sources/wasip3_tls.c sources/__wasm_init_task.S ) endif() diff --git a/libc-bottom-half/cloudlibc/src/libc/errno/errno.c b/libc-bottom-half/cloudlibc/src/libc/errno/errno.c index 368255b60..91a739074 100644 --- a/libc-bottom-half/cloudlibc/src/libc/errno/errno.c +++ b/libc-bottom-half/cloudlibc/src/libc/errno/errno.c @@ -86,4 +86,7 @@ static_assert(ETXTBSY == __WASI_ERRNO_TXTBSY, "Value mismatch"); static_assert(EXDEV == __WASI_ERRNO_XDEV, "Value mismatch"); #endif +// `` may define `errno` as a call to `__errno_location`, so undo that +// here where the thread-local it ultimately refers to is defined. +#undef errno thread_local int errno = 0; diff --git a/libc-bottom-half/crt/wasip3_symbol_references.h b/libc-bottom-half/crt/wasip3_symbol_references.h index 10251ccbb..b047054c4 100644 --- a/libc-bottom-half/crt/wasip3_symbol_references.h +++ b/libc-bottom-half/crt/wasip3_symbol_references.h @@ -16,10 +16,9 @@ __attribute__((used)) static void *__wasm_init_async_task_ref = __attribute__((used)) static void *cabi_realloc_ref = cabi_realloc; // Force `__wasm_{g,s}et_{stack_pointer,tls_base}` to exist as defined symbols. -// These end up as imported functions which `wit-component` recognizes. Note -// that for coop threads the tls_base symbols are defined but without coop -// threads they're defined within libc itself to access a locally-defined -// global (see `wasip3_non_coop_tls_base.S`). +// These end up as imported functions which `wit-component` recognizes, and what +// exactly they're hooked up to will depend on `wit-component` when this is +// turned into a component. __asm__( ".globl __wasm_get_stack_pointer\n" ".type __wasm_get_stack_pointer,@function\n" @@ -33,7 +32,6 @@ __asm__( ".import_module __wasm_set_stack_pointer, \"env\"\n" ".import_name __wasm_set_stack_pointer, \"__wasm_set_stack_pointer\"\n" -#ifdef __wasi_cooperative_threads__ ".globl __wasm_get_tls_base\n" ".type __wasm_get_tls_base,@function\n" ".functype __wasm_get_tls_base () -> (i32)\n" @@ -45,8 +43,7 @@ __asm__( ".functype __wasm_set_tls_base (i32) -> ()\n" ".import_module __wasm_set_tls_base, \"env\"\n" ".import_name __wasm_set_tls_base, \"__wasm_set_tls_base\"\n" -#endif -); +); #endif diff --git a/libc-bottom-half/headers/public/__errno.h b/libc-bottom-half/headers/public/__errno.h deleted file mode 100644 index 008245d97..000000000 --- a/libc-bottom-half/headers/public/__errno.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef __wasilibc___errno_h -#define __wasilibc___errno_h - -#ifdef __cplusplus -extern "C" { -#endif - -extern _Thread_local int errno; - -#define errno errno - -#ifdef __cplusplus -} -#endif -#endif diff --git a/libc-bottom-half/sources/__cabi_realloc_wrapper.S b/libc-bottom-half/sources/__cabi_realloc_wrapper.S index 5b9b87e02..62de72b4b 100644 --- a/libc-bottom-half/sources/__cabi_realloc_wrapper.S +++ b/libc-bottom-half/sources/__cabi_realloc_wrapper.S @@ -27,11 +27,23 @@ // For coop-threads builds we need to configure TLS to the initial TLS // value for all tasks/etc, so import those symbols here. +// +// Note that this saves and restores context slot 1 itself rather than this +// module's TLS base. When several modules are linked together the slot holds an +// array of per-module base pointers, so clobbering just libc's entry would both +// leave the other modules pointing at the wrong TLS and scribble through +// whatever stale pointer the slot happened to contain. #ifdef __wasi_cooperative_threads__ -.functype __wasm_set_tls_base (i32) -> () -.functype __wasm_get_tls_base () -> (i32) -.type __wasm_set_tls_base,@function -.type __wasm_get_tls_base,@function +.functype wasip3_context_set_1 (i32) -> () +.functype wasip3_context_get_1 () -> (i32) +.functype __wasilibc_set_task_tls (i32) -> () +.type wasip3_context_set_1,@function +.type wasip3_context_get_1,@function +.type __wasilibc_set_task_tls,@function +.import_module wasip3_context_get_1, "$root" +.import_name wasip3_context_get_1, "[context-get-1]" +.import_module wasip3_context_set_1, "$root" +.import_name wasip3_context_set_1, "[context-set-1]" .type __init_tls_base,@global #ifdef __PIC__ .globaltype __init_tls_base, i32 @@ -68,7 +80,7 @@ cabi_realloc: call __wasm_get_stack_pointer local.set 4 #ifdef __wasi_cooperative_threads__ - call __wasm_get_tls_base + call wasip3_context_get_1 local.set 5 #endif @@ -85,7 +97,7 @@ cabi_realloc: // Next up configure TLS. This is only required in coop-threads builds. #ifdef __wasi_cooperative_threads__ global.get __init_tls_base - call __wasm_set_tls_base + call __wasilibc_set_task_tls #endif // And finally forward to the actual implementation of `cabi_realloc` @@ -100,7 +112,7 @@ cabi_realloc: call __wasm_set_stack_pointer #ifdef __wasi_cooperative_threads__ local.get 5 - call __wasm_set_tls_base + call wasip3_context_set_1 #endif end_function diff --git a/libc-bottom-half/sources/__errno_location.c b/libc-bottom-half/sources/__errno_location.c index 643c7883b..959b198ed 100644 --- a/libc-bottom-half/sources/__errno_location.c +++ b/libc-bottom-half/sources/__errno_location.c @@ -1,3 +1,8 @@ #include +// `` may define `errno` as a call to this function, so reach the +// underlying thread-local directly here to avoid recursing into ourselves. +#undef errno +extern _Thread_local int errno; + int *__errno_location(void) { return &errno; } diff --git a/libc-bottom-half/sources/__wasm_init_task.S b/libc-bottom-half/sources/__wasm_init_task.S index 2ea6e1d3a..531d27018 100644 --- a/libc-bottom-half/sources/__wasm_init_task.S +++ b/libc-bottom-half/sources/__wasm_init_task.S @@ -12,7 +12,7 @@ #else .globaltype __init_tls_base, i32, immutable #endif -.functype __wasm_set_tls_base (i32) -> () +.functype __wasilibc_set_task_tls (i32) -> () #endif @@ -30,10 +30,10 @@ // the same TLS values are used across all tasks, which seem reasonable-enough // for now. // -// Note that configuring TLS is only required in coop threads builds since when -// coop-threads are disabled TLS is routed through a global in wasi-libc which -// needs no extra handling (unlike coop threads where TLS routes through -// `context.{get,set}` which needs setting up). +// Note that configuring TLS is only required in coop threads builds. When +// threads are disabled TLS is handled by `wit-component`'s synthesis of +// functions. With coop threads though delegation goes into C in wasi-libc +// itself to do a bit of detection logic. .globl __wasm_init_task .export_name __wasm_init_task, __wasm_init_task .type __wasm_init_task,@function @@ -45,7 +45,7 @@ __wasm_init_task: #ifdef __wasi_cooperative_threads__ global.get __init_tls_base - call __wasm_set_tls_base + call __wasilibc_set_task_tls #endif end_function diff --git a/libc-bottom-half/sources/isatty.c b/libc-bottom-half/sources/isatty.c index e086ac209..707fe83c7 100644 --- a/libc-bottom-half/sources/isatty.c +++ b/libc-bottom-half/sources/isatty.c @@ -1,4 +1,3 @@ -#include <__errno.h> #include <__function___isatty.h> #include #include diff --git a/libc-bottom-half/sources/netdb.c b/libc-bottom-half/sources/netdb.c index 4c27ad7c2..c1cd92799 100644 --- a/libc-bottom-half/sources/netdb.c +++ b/libc-bottom-half/sources/netdb.c @@ -21,8 +21,15 @@ static_assert(SOCK_STREAM == __WASI_FILETYPE_SOCKET_STREAM, "value mismatch"); NETWORK_ERROR_CODE_PERMANENT_RESOLVER_FAILURE #endif +// `` may define `h_errno` as a call to `__h_errno_location`, so undo +// that here where the thread-local it ultimately refers to is defined. +#undef h_errno _Thread_local int h_errno = 0; +#if !defined(__wasip1__) && !defined(__wasip2__) +int *__h_errno_location(void) { return &h_errno; } +#endif + static struct servent global_serv = {0}; static int map_error(ip_name_lookup_error_code_t *error) { diff --git a/libc-bottom-half/sources/wasip3_non_coop_tls_base.S b/libc-bottom-half/sources/wasip3_non_coop_tls_base.S deleted file mode 100644 index ee964f331..000000000 --- a/libc-bottom-half/sources/wasip3_non_coop_tls_base.S +++ /dev/null @@ -1,30 +0,0 @@ -// On WASIPp3 when coop threads are disabled the `__wasm_{g,s}et_tls_base` -// symbols are defined here instead of imported into the module to avoid using -// context slot 1. The definition here is in terms of a local wasm global. - -#include - -#if defined(__wasm_libcall_thread_context__) && !defined(__wasi_cooperative_threads__) - .globl __wasm_get_tls_base - .type __wasm_get_tls_base,@function - .globl __wasm_set_tls_base - .type __wasm_set_tls_base,@function - - // When coop threads are disabled do something similar to the basic output - // which is to have a global that stores the TLS base. - .globaltype __wasilibc_tls_base, i32 - -__wasm_get_tls_base: - .functype __wasm_get_tls_base () -> (i32) - global.get __wasilibc_tls_base - end_function - -__wasm_set_tls_base: - .functype __wasm_set_tls_base (i32) -> () - local.get 0 - global.set __wasilibc_tls_base - end_function - - .section .wasm.__wasilibc_tls_base,"",@ -__wasilibc_tls_base: -#endif diff --git a/libc-bottom-half/sources/wasip3_tls.c b/libc-bottom-half/sources/wasip3_tls.c new file mode 100644 index 000000000..88fcb6ccf --- /dev/null +++ b/libc-bottom-half/sources/wasip3_tls.c @@ -0,0 +1,23 @@ +#ifdef __wasm_libcall_thread_context__ + +#include +#include + +#ifdef __wasi_cooperative_threads__ + +void __wasilibc_set_task_tls(void *init_tls_base) { + const struct __wasilibc_program_tls_info *info = + __wasilibc_program_tls_info(); + + // With a single module in the component the TLS base is stored directly in + // context slot 1, so this module's own initial TLS is what belongs there. + // With more than one module the slot instead holds the array of per-module + // TLS base pointers, and the main thread's array was populated by each module + // as the component was instantiated. + wasip3_context_set_1(info == NULL ? init_tls_base + : (void *)info->main_thread_tls_base); +} + +#endif // __wasi_cooperative_threads__ + +#endif // __wasm_libcall_thread_context__ diff --git a/libc-top-half/headers/private/wasi/wasip3_tls.h b/libc-top-half/headers/private/wasi/wasip3_tls.h new file mode 100644 index 000000000..0e8fd0d70 --- /dev/null +++ b/libc-top-half/headers/private/wasi/wasip3_tls.h @@ -0,0 +1,48 @@ +// Internal helpers for managing thread-local storage on the `wasm32-wasip3` +// target. +// +// See https://github.com/WebAssembly/wasi-libc/issues/857 for more info on the +// design. + +#ifndef __WASI_WASIP3_TLS_H +#define __WASI_WASIP3_TLS_H + +#include +#include + +#ifdef __wasm_libcall_thread_context__ + +struct __wasilibc_library_tls_info { + size_t tls_size; + size_t tls_align; + void (*init_tls)(void *); +}; + +struct __wasilibc_program_tls_info { + size_t num_libraries; + const struct __wasilibc_library_tls_info *library_info; + void **main_thread_tls_base; +}; + +// Synthesized by `wit-component` for multi-module programs only, hence the weak +// declaration: a null address means this is a single-module program. +extern const struct __wasilibc_program_tls_info __wasm_program_tls_info + __attribute__((__weak__)); + +// Returns the program's TLS description, or null if this is a single module. +static inline const struct __wasilibc_program_tls_info * +__wasilibc_program_tls_info(void) { + return &__wasm_program_tls_info; +} + +#ifdef __wasi_cooperative_threads__ + +// Sets up context slot 1 for the currently running task. Invoked from inline +// assembly from wrappers of exported functions plus the cabi_realloc export wrapper. +void __wasilibc_set_task_tls(void *init_tls_base); + +#endif // __wasi_cooperative_threads__ + +#endif // __wasm_libcall_thread_context__ + +#endif // __WASI_WASIP3_TLS_H diff --git a/libc-top-half/musl/include/errno.h b/libc-top-half/musl/include/errno.h index 0252e1b09..91b1fca17 100644 --- a/libc-top-half/musl/include/errno.h +++ b/libc-top-half/musl/include/errno.h @@ -9,15 +9,23 @@ extern "C" { #ifdef __wasilibc_unmodified_upstream /* Use alternate WASI libc headers */ #include +#else +#include <__errno_values.h> +#endif #ifdef __GNUC__ __attribute__((const)) #endif int *__errno_location(void); + +// Cross-module TLS does not work in WASIp3, so declaring an `extern` to some +// data doesn't work for errno. On other platforms though that's been the +// historical default so that's left in place. +#if !defined(__wasip1__) && !defined(__wasip2__) #define errno (*__errno_location()) #else -#include <__errno.h> -#include <__errno_values.h> +extern _Thread_local int errno; +#define errno errno #endif #ifdef _GNU_SOURCE diff --git a/libc-top-half/musl/include/netdb.h b/libc-top-half/musl/include/netdb.h index 5f6008a43..5cb5473aa 100644 --- a/libc-top-half/musl/include/netdb.h +++ b/libc-top-half/musl/include/netdb.h @@ -121,7 +121,8 @@ struct hostent *gethostbyaddr (const void *, socklen_t, int); #ifdef __GNUC__ __attribute__((const)) #endif -#ifdef __wasilibc_unmodified_upstream +// See `` for why this differs on wasip3 +#if !defined(__wasip1__) && !defined(__wasip2__) int *__h_errno_location(void); #define h_errno (*__h_errno_location()) #elif !(defined __wasip1__) diff --git a/libc-top-half/musl/src/env/__init_tls.c b/libc-top-half/musl/src/env/__init_tls.c index 86674109a..e3d9e7623 100644 --- a/libc-top-half/musl/src/env/__init_tls.c +++ b/libc-top-half/musl/src/env/__init_tls.c @@ -285,42 +285,3 @@ static void static_init_tls(size_t *aux) weak_alias(static_init_tls, __init_tls); #endif - -#ifdef __wasi_cooperative_threads__ -// Entrypoint for all new async tasks, invoked from `__wasm_init_async_task`. -// -// This is responsible for allocating a new stack for this async task in -// addition to initializing TLS. TLS right now is stored at the top of the -// stack. -hidden void* __wasilibc_init_async_task(void) { - // Attempt to use the same stack size as the LLD-initialized stack (as - // reported by `get_stack_bounds`). If that failed (e.g. in PIC) mode then - // choose a best-effort constant. - size_t stack_size = get_stack_bounds().size; - if (stack_size == 0) - stack_size = __default_stacksize; - - // Allocate the stack, trapping if allocation fails. - // - // FIXME(#810) this is never deallocated. - void* task_stack = malloc(stack_size); - if (task_stack == NULL) - __builtin_trap(); - - // TLS is stored at the top of the stack, and its initial address is - // aligned-down based on its requirement. Note that `__wasm_init_tls` serves - // double-duty of initializing using `memory.init` to initialize TLS while - // additionally setting the TLS base for this task. - uintptr_t stack_top = (uintptr_t)task_stack + stack_size; - uintptr_t tls_base_unaligned = stack_top - __builtin_wasm_tls_size(); - uintptr_t tls_base = tls_base_unaligned & -__builtin_wasm_tls_align(); - assert(tls_base >= (uintptr_t) task_stack); - __wasm_init_tls((void*)tls_base); - - // The stack itself is always 16-byte aligned, so align down as necessary. - // Return this so the assembly shim can set this as the current stack pointer. - uintptr_t stack_init_aligned = tls_base & -16; - assert(stack_init_aligned >= (uintptr_t) task_stack); - return (void*) stack_init_aligned; -} -#endif diff --git a/libc-top-half/musl/src/thread/coop-threads/pthread_create.c b/libc-top-half/musl/src/thread/coop-threads/pthread_create.c index 851db0d0a..848d7a680 100644 --- a/libc-top-half/musl/src/thread/coop-threads/pthread_create.c +++ b/libc-top-half/musl/src/thread/coop-threads/pthread_create.c @@ -5,11 +5,13 @@ #include "stdio_impl.h" #include #include +#include #include #include #include #include +#include // These are implemented elsewhere static void dummy_0() {} @@ -27,10 +29,6 @@ static void process_map_base_deferred_free() { // this is the component-model entrypoint. extern void __wasi_coop_thread_start(void *context); -// Synthesized by wasm-ld this is called at the start of each new thread to -// initialize its own TLS block. -extern void __wasm_init_tls(void *ptr); - // There is currently no thread.exit intrinsic, so pthread_exit // cannot terminate a thread early. As such, we do not expose it to users. // TODO(wasip3) revisit this if we add a thread.exit intrinsic @@ -117,6 +115,99 @@ void __do_cleanup_pop(struct __ptcb *cb) { __pthread_self()->cancelbuf = cb->__next; } +// Synthesized by `wasm-ld` for this module; copies libc's TLS image to the +// given address and makes it libc's TLS base. +extern void __wasm_init_tls(void *); + +// Rounds `value` up to a multiple of `align`, which must be a power of two. +static uintptr_t align_up(uintptr_t value, size_t align) { + return (value + (align - 1)) & -(uintptr_t)align; +} + +// The index of libc's own entry in `__wasm_program_tls_info`, or `SIZE_MAX` if +// it hasn't been looked up yet. +// +// `wit-component` assigns these indices when linking and has no idea which +// library is libc, so this is discovered by looking for the entry matching +// libc's current TLS base. Racing threads will all compute the same answer. +static size_t libc_index = SIZE_MAX; + +static size_t libc_tls_index(const struct __wasilibc_program_tls_info *info) { + if (libc_index == SIZE_MAX) { + void **current = wasip3_context_get_1(); + void *base = __builtin_wasm_tls_base(); + for (size_t i = 0; i < info->num_libraries; i++) { + if (current[i] == base) { + libc_index = i; + break; + } + } + } + return libc_index; +} + +// Computes the amount of memory a new thread needs for the thread-local storage +// of every library in the program, storing the required alignment in `*align`. +// +// For a single-module program this is just this module's own TLS. +static size_t thread_tls_size(size_t *align) { + const struct __wasilibc_program_tls_info *info = + __wasilibc_program_tls_info(); + if (info == NULL) { + *align = __builtin_wasm_tls_align(); + return __builtin_wasm_tls_size(); + } + + // The array of per-library TLS base pointers goes first, followed by each + // library's own block at its required alignment. + uintptr_t size = info->num_libraries * sizeof(void *); + size_t result_align = _Alignof(void *); + + for (size_t i = 0; i < info->num_libraries; i++) { + const struct __wasilibc_library_tls_info *library = &info->library_info[i]; + size = align_up(size, library->tls_align) + library->tls_size; + if (library->tls_align > result_align) + result_align = library->tls_align; + } + + *align = result_align; + return align_up(size, result_align); +} + +// Carves `block`, which must be at least `thread_tls_size()` bytes and suitably +// aligned, into a TLS region per library. +// +// This only computes the layout; no TLS is initialized and the currently +// running thread is left alone, so this is safe to call from the thread which +// is spawning a new one. The return value is what context slot 1 takes on for +// the new thread. +static void *layout_thread_tls(void *block) { + const struct __wasilibc_program_tls_info *info = + __wasilibc_program_tls_info(); + if (info == NULL) + return block; + + void **bases = block; + uintptr_t next = (uintptr_t)block + info->num_libraries * sizeof(void *); + for (size_t i = 0; i < info->num_libraries; i++) { + const struct __wasilibc_library_tls_info *library = &info->library_info[i]; + next = align_up(next, library->tls_align); + bases[i] = (void *)next; + next += library->tls_size; + } + return bases; +} + +// Returns the base of libc's own TLS within a layout produced by +// `layout_thread_tls`. +static void *libc_tls_base_in_layout(void *layout) { + const struct __wasilibc_program_tls_info *info = + __wasilibc_program_tls_info(); + if (info == NULL) + return layout; + return ((void **)layout)[libc_tls_index(info)]; +} + struct start_args { // Note that this `stack` member must be first as its offset is referenced // from `__wasi_coop_thread_start` within the `__wasi_coop_thread_start.s` @@ -125,12 +216,13 @@ struct start_args { // Otherwise though this is the initial stack pointer of the thread-to-be. void *stack; - // The TLS base pointer for this new thread. Not yet initialized, but - // allocated and has appropriate alignment. - void *tls_base; + // This new thread's thread-local storage, as laid out by + // `layout_thread_tls`. Not yet initialized, but allocated with + // appropriate size and alignment for every library in the program. + void *tls_layout; // A pointer to where this thread's `struct pthread` block is located. This is - // within `tls_base` and is used during initialization. + // within libc's portion of `tls_layout` and is used during initialization. pthread_t self; // The arguments used to execute this thread. @@ -139,19 +231,35 @@ struct start_args { }; hidden void __wasi_coop_thread_start_C(struct start_args *args) { + const struct __wasilibc_program_tls_info *info = + __wasilibc_program_tls_info(); + // First thing to do on this new thread is initialize TLS. At the start of a - // new thread our tls base is 0, so it needs to be set to `args->tls_base`. - // TLS initialization happens through the `wasm-ld` provided symbol of - // `__wasm_init_tls` which will `memory.init` this pointer and additionally - // store the pointer into our tls base slot. + // new thread our TLS is not configured at all, so it needs to be set to + // `args->tls_layout`. That both installs the layout as this task's TLS and + // runs each library's `__wasm_init_tls`, which `memory.init`s that library's + // TLS image into place. // - // Note that this thread's own `struct pthread`, however, lives within the TLS - // block and is already initialized. We don't want the default initialization - // here as well. The contents of our pthread block are thus saved/restored - // around the `__wasm_init_tls` call. - assert(((uintptr_t)args->tls_base) % __builtin_wasm_tls_align() == 0); + // Note that this thread's own `struct pthread`, however, lives within libc's + // TLS block and is already initialized. We don't want the default + // initialization here as well. The contents of our pthread block are thus + // saved/restored around the TLS initialization. + // + // If this is a single-mdoule component, meaning `info` is NULL, then we can + // directly use `__wasm_init_tls` to initialize both this thread's TLS block + // and the TLS base pointer living in `context.{get,set} 1`. Otherwise + // though this manually sets `context.set 1`, which each module's synthesized + // accessor reads from (synthesized by `wit-component`). struct pthread self_copy = *args->self; - __wasm_init_tls(args->tls_base); + if (info == NULL) { + __wasm_init_tls(args->tls_layout); + } else { + wasip3_context_set_1(args->tls_layout); + void **bases = args->tls_layout; + size_t num_libraries = info->num_libraries; + for (size_t i = 0; i < num_libraries; i++) + info->library_info[i].init_tls(bases[i]); + } *__pthread_self() = self_copy; // Our tid should be configured in `pthread_create`, but double check it. @@ -189,10 +297,10 @@ int __pthread_create(pthread_t *restrict res, pthread_attr_t attr = {0}; - size_t tls_size = __builtin_wasm_tls_size(); - size_t tls_align = __builtin_wasm_tls_align(); - void *tls_base = __builtin_wasm_tls_base(); - void *new_tls_base; + size_t tls_align; + size_t tls_size = thread_tls_size(&tls_align); + void *libc_tls_base = __builtin_wasm_tls_base(); + void *new_tls_layout; size_t tls_offset; /* We'll need to allocate space for a correctly-aligned TLS block, so adjust the size accordingly. */ @@ -253,12 +361,18 @@ int __pthread_create(pthread_t *restrict res, } } - void *unaligned_tls_base = tsd - tls_size; - new_tls_base = (void *)(((uintptr_t)unaligned_tls_base) & -tls_align); + // Place the TLS block at the very top of the region reserved for it, right + // below the thread-specific data, so that aligning its base down stays above + // `stack`. + void *unaligned_tls_layout_base = tsd - (tls_size - tls_align); + new_tls_layout = layout_thread_tls( + (void *)(((uintptr_t)unaligned_tls_layout_base) & -tls_align)); - /* Compute pthread struct offset from old TLS base, apply to new TLS base */ - tls_offset = (uintptr_t)self - (uintptr_t)tls_base; - new = (void *)((uintptr_t)new_tls_base + tls_offset); + // Compute pthread struct offset from old TLS base, apply to the new thread's + // libc TLS base. + tls_offset = (uintptr_t)self - (uintptr_t)libc_tls_base; + new = + (void *)((uintptr_t)libc_tls_base_in_layout(new_tls_layout) + tls_offset); // Ensure the pthread structure is at least fully initialized. memset(new, 0, sizeof(*new)); @@ -297,7 +411,7 @@ int __pthread_create(pthread_t *restrict res, args->stack = new->stack; /* just for convenience of asm trampoline */ args->start_func = entry; args->start_arg = arg; - args->tls_base = (void *)new_tls_base; + args->tls_layout = new_tls_layout; args->self = new; if (!libc.threads_minus_1++) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4a2ae6473..21dedfb65 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -288,14 +288,83 @@ function(add_test_pair test_name test_file) # If shared libraries are enabled, and this test supports shared libraries, # then build a version of the test that's linked against `libc.so` and run the # test that way. This is skipped on WASIp1 since the format used here requires - # components, and WASIp3 coop threads testing is the subject of #813 - if(NOT arg_NOSHARED AND BUILD_SHARED AND NOT (WASI MATCHES "p1") - AND NOT (WASI MATCHES "p3" AND HAVE_WASM_LIBCALL_THREAD_CONTEXT)) + # components. + if(NOT arg_NOSHARED AND BUILD_SHARED AND NOT (WASI MATCHES "p1")) add_test_executable("shared_${test_name}" ${test_file} SHARED ${ARGN}) register_test("shared_${test_name}" "shared_${test_name}" ${ARGN}) endif() endfunction() +# Builds a shared library out of `test/src/shared/${name}.c` +# for use by `add_shared_library_test` below. +# +# Optional arguments: +# +# * `NEEDS a b c` - other test shared libraries this one links against +function(add_test_shared_library name) + set(options) + set(oneValueArgs) + set(multiValueArgs NEEDS) + cmake_parse_arguments(PARSE_ARGV 1 arg "${options}" "${oneValueArgs}" "${multiValueArgs}") + + add_library(${name} SHARED EXCLUDE_FROM_ALL + "${CMAKE_CURRENT_SOURCE_DIR}/src/shared/${name}.c") + set_pic(${name}) + target_compile_options(${name} PRIVATE -fvisibility=default) + set_target_properties(${name} PROPERTIES NO_SONAME 1) + add_dependencies(${name} sysroot builtins) + clang_format_target(${name}) + + if (ENABLE_COOP_THREADS) + target_compile_options(${name} PRIVATE -pthread) + target_link_options(${name} PRIVATE -pthread) + endif() + + foreach(needed IN LISTS arg_NEEDS) + target_link_libraries(${name} PRIVATE ${needed}) + endforeach() +endfunction() + +# Adds a test which links a program against one or more test shared libraries +# built by `add_test_shared_library`, in addition to `libc.so`. +# +# Unlike `add_test_pair` there is no statically linked counterpart here: the +# whole point of these tests is to exercise a component built out of several +# separate modules, which only exists in the shared configuration. +# +# * `test_file` is a path relative to the `src` directory. +# * `LIBS a b c` names the test shared libraries to link against. +# +# All other arguments are forwarded to `add_test_executable` and `register_test`. +function(add_shared_library_test test_file) + set(options) + set(oneValueArgs) + set(multiValueArgs LIBS) + cmake_parse_arguments(PARSE_ARGV 1 arg "${options}" "${oneValueArgs}" "${multiValueArgs}") + + cmake_path(REPLACE_EXTENSION test_file wasm OUTPUT_VARIABLE test_name) + set(test_name "shared_${test_name}") + set(test_file "${CMAKE_CURRENT_SOURCE_DIR}/src/${test_file}") + + if (NOT BUILD_SHARED OR WASI MATCHES "p1") + return() + endif() + + set(link_flags) + set(link_libs) + foreach(lib IN LISTS arg_LIBS) + list(APPEND link_flags -l${lib}) + list(APPEND link_libs "$") + endforeach() + + add_test_executable(${test_name} ${test_file} SHARED + LDFLAGS -L${CMAKE_CURRENT_BINARY_DIR} ${link_flags} + SHARED_LIBS ${link_libs} + DEPENDENCIES ${arg_LIBS} + ${arg_UNPARSED_ARGUMENTS}) + register_test(${test_name} ${test_name} ${arg_UNPARSED_ARGUMENTS}) +endfunction() + # Adds a new test from the `libc-test` repository where `test_file` is a # relative path from the `src` directory. # @@ -484,7 +553,8 @@ if ( if (CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 23.0) set(noshared) endif() - add_wasilibc_test(setjmp.c SETJMP ${noshared}) + add_wasilibc_test(setjmp.c SETJMP ${noshared} + SHARED_LIBS "${SYSROOT_LIB}/libsetjmp.so") endif() # This test works for single-threaded targets too @@ -508,6 +578,18 @@ if (TARGET_TRIPLE MATCHES "-threads") target_include_directories(pthread_mutex_busywait.wasm PRIVATE ${LIBC_TEST}) endif() +# ========= multiple-shared-library tests ======================== +# +# Tests for exercising cross-library interactions. All tests are by default +# executed with shared libraries, but this only exercises interactions between +# the main program and libc. These tests interactions between libraries +# themselves. + +add_test_shared_library(tls_a) +add_test_shared_library(tls_b NEEDS tls_a) +add_test_shared_library(tls_none) +add_shared_library_test(multi_tls.c LIBS tls_a tls_b tls_none) + # ========= cooperative/not threads related tests ================ if (ENABLE_COOP_THREADS) diff --git a/test/src/multi_tls.c b/test/src/multi_tls.c new file mode 100644 index 000000000..5c55264f4 --- /dev/null +++ b/test/src/multi_tls.c @@ -0,0 +1,216 @@ +#include "shared/tls_a.h" +#include "shared/tls_b.h" +#include "shared/tls_none.h" +#include "test.h" + +#include +#include +#include +#include +#include + +_Thread_local int main_counter = 0x0c0c0c0c; +_Thread_local char main_name[16] = "main"; + +static int overlaps(void *a, size_t a_size, void *b, size_t b_size) { + uintptr_t a_start = (uintptr_t)a; + uintptr_t b_start = (uintptr_t)b; + return a_start < b_start + b_size && b_start < a_start + a_size; +} + +#define THREADS 4 + +static pthread_barrier_t barrier; + +struct thread_args { + int index; + void *a_addr; + void *b_addr; + void *main_addr; +}; + +static void assert_initial_tls() { + if (a_counter_get() != A_COUNTER_INIT) + t_error("a_counter = %#x, want %#x\n", a_counter_get(), A_COUNTER_INIT); + if (strcmp(a_name_get(), A_NAME_INIT) != 0) + t_error("a_name = \"%s\", want \"%s\"\n", a_name_get(), A_NAME_INIT); + if (a_zeroed_sum() != 0) + t_error("a_zeroed_sum = %d, want 0\n", a_zeroed_sum()); + if (b_counter_get() != B_COUNTER_INIT) + t_error("b_counter = %#llx, want %#llx\n", b_counter_get(), + (long long)B_COUNTER_INIT); + if (main_counter != 0x0c0c0c0c) + t_error("main_counter = %#x, want 0x0c0c0c0c\n", main_counter); + for (size_t i = 0; i < B_BLOB_SIZE; i++) { + if (b_blob_get(i) != 0) + t_error("b_blob[%zu] = %d, want 0\n", i, b_blob_get(i)); + } + + // Alignment has to hold for every thread's block, not just the main one. + if ((uintptr_t)b_aligned_addr() % B_ALIGN != 0) + t_error("b_aligned is at %p, want %d-byte alignment\n", b_aligned_addr(), B_ALIGN); + + // Each library's thread-locals must live in disjoint memory. + void *a_addr = a_counter_addr(); + void *b_addr = b_counter_addr(); + void *main_addr = &main_counter; + if (overlaps(a_addr, sizeof(int), b_addr, sizeof(long long))) + t_error("libtls_a and libtls_b share TLS (%p vs %p)\n", a_addr, b_addr); + if (overlaps(a_addr, sizeof(int), main_addr, sizeof(int))) + t_error("libtls_a and the program share TLS (%p vs %p)\n", a_addr, + main_addr); + if (overlaps(b_addr, sizeof(long long), main_addr, sizeof(int))) + t_error("libtls_b and the program share TLS (%p vs %p)\n", b_addr, + main_addr); +} + +static void clobber_tls(int index) { + int value = 0x1000 + index; + a_counter_set(value); + b_counter_set(value + 1); + main_counter = value + 2; + a_zeroed_fill(index + 1); + for (size_t i = 0; i < B_BLOB_SIZE; i++) + b_blob_set(i, value + (int)i); + + char name[A_NAME_SIZE]; + snprintf(name, sizeof(name), "thread-%d", index); + a_name_set(name); +} + +static void assert_clobbered_tls(int index) { + int value = 0x1000 + index; + char name[A_NAME_SIZE]; + snprintf(name, sizeof(name), "thread-%d", index); + if (a_counter_get() != value) + t_error("thread %d: a_counter = %#x, want %#x\n", index, + a_counter_get(), value); + if (b_counter_get() != value + 1) + t_error("thread %d: b_counter = %#llx, want %#x\n", index, + b_counter_get(), value + 1); + if (main_counter != value + 2) + t_error("thread %d: main_counter = %#x, want %#x\n", index, + main_counter, value + 2); + if (strcmp(a_name_get(), name) != 0) + t_error("thread %d: a_name = \"%s\", want \"%s\"\n", index, + a_name_get(), name); + if (a_zeroed_sum() != (index + 1) * 8) + t_error("thread %d: a_zeroed_sum = %d, want %d\n", index, + a_zeroed_sum(), (index + 1) * 8); + for (size_t i = 0; i < B_BLOB_SIZE; i++) { + if (b_blob_get(i) != value + (int)i) + t_error("thread %d: b_blob[%zu] = %#x, want %#x\n", index, i, + b_blob_get(i), value + (int)i); + } + if (b_read_a_counter() != value) + t_error("thread %d: b_read_a_counter = %#x, want %#x\n", index, + b_read_a_counter(), value); +} + +static void *thread_main(void *raw) { + struct thread_args *args = raw; + + assert_initial_tls(); + + args->a_addr = a_counter_addr(); + args->b_addr = b_counter_addr(); + args->main_addr = &main_counter; + + pthread_barrier_wait(&barrier); + clobber_tls(args->index); + + pthread_barrier_wait(&barrier); + assert_clobbered_tls(args->index); + + return NULL; +} + +static void test_threads(void) { + a_counter_set(0xdead); + b_counter_set(0xbeef); + main_counter = 0xf00d; + a_name_set("main-thread"); + a_zeroed_fill(99); + for (size_t i = 0; i < B_BLOB_SIZE; i++) + b_blob_set(i, 0x7777); + + int err = pthread_barrier_init(&barrier, NULL, THREADS); + if (err != 0) + t_error("pthread_barrier_init failed: %d\n", err); + + pthread_t threads[THREADS]; + struct thread_args args[THREADS] = {0}; + for (int i = 0; i < THREADS; i++) { + args[i].index = i; + err = pthread_create(&threads[i], NULL, thread_main, &args[i]); + if (err != 0) { +#ifdef __wasi_cooperative_threads__ + t_error("pthread_create(%d) failed: %d\n", i, err); +#else + return; +#endif + } + } + for (int i = 0; i < THREADS; i++) { + err = pthread_join(threads[i], NULL); + if (err != 0) + t_error("pthread_join(%d) failed: %d\n", i, err); + } + pthread_barrier_destroy(&barrier); + + // No two threads, and no thread and the main thread, may share a block. + for (int i = 0; i < THREADS; i++) { + if (args[i].a_addr == a_counter_addr()) + t_error("thread %d shares libtls_a TLS with the main thread (%p)\n", i, + args[i].a_addr); + if (args[i].b_addr == b_counter_addr()) + t_error("thread %d shares libtls_b TLS with the main thread (%p)\n", i, + args[i].b_addr); + if (args[i].main_addr == (void *)&main_counter) + t_error("thread %d shares program TLS with the main thread (%p)\n", i, + args[i].main_addr); + for (int j = i + 1; j < THREADS; j++) { + if (args[i].a_addr == args[j].a_addr) + t_error("threads %d and %d share libtls_a TLS (%p)\n", i, j, + args[i].a_addr); + if (args[i].b_addr == args[j].b_addr) + t_error("threads %d and %d share libtls_b TLS (%p)\n", i, j, + args[i].b_addr); + if (args[i].main_addr == args[j].main_addr) + t_error("threads %d and %d share program TLS (%p)\n", i, j, + args[i].main_addr); + } + } + + // Finally, the main thread's own TLS must have survived all of that. + if (a_counter_get() != 0xdead) + t_error("main a_counter = %#x, want 0xdead\n", a_counter_get()); + if (b_counter_get() != 0xbeef) + t_error("main b_counter = %#llx, want 0xbeef\n", b_counter_get()); + if (main_counter != 0xf00d) + t_error("main main_counter = %#x, want 0xf00d\n", main_counter); + if (strcmp(a_name_get(), "main-thread") != 0) + t_error("main a_name = \"%s\", want \"main-thread\"\n", a_name_get()); + if (a_zeroed_sum() != 99 * 8) + t_error("main a_zeroed_sum = %d, want %d\n", a_zeroed_sum(), 99 * 8); + for (size_t i = 0; i < B_BLOB_SIZE; i++) { + if (b_blob_get(i) != 0x7777) + t_error("main b_blob[%zu] = %#x, want 0x7777\n", i, b_blob_get(i)); + } +} + +int main(void) { + assert_initial_tls(); + clobber_tls(0); + assert_clobbered_tls(0); + + if (none_add(2, 3) != 5) + t_error("none_add(2, 3) = %d, want 5\n", none_add(2, 3)); + none_state_set(7); + if (none_state_get() != 7) + t_error("none_state_get = %d, want 7\n", none_state_get()); + + test_threads(); + + return t_status; +} diff --git a/test/src/shared/tls_a.c b/test/src/shared/tls_a.c new file mode 100644 index 000000000..618714ef8 --- /dev/null +++ b/test/src/shared/tls_a.c @@ -0,0 +1,32 @@ +#include "tls_a.h" + +#include + +_Thread_local int a_counter = A_COUNTER_INIT; +_Thread_local char a_name[A_NAME_SIZE] = A_NAME_INIT; +_Thread_local int a_zeroed[8]; + +int a_counter_get(void) { return a_counter; } + +void a_counter_set(int value) { a_counter = value; } + +void *a_counter_addr(void) { return &a_counter; } + +const char *a_name_get(void) { return a_name; } + +void a_name_set(const char *value) { + strncpy(a_name, value, A_NAME_SIZE - 1); + a_name[A_NAME_SIZE - 1] = '\0'; +} + +int a_zeroed_sum(void) { + int sum = 0; + for (size_t i = 0; i < sizeof(a_zeroed) / sizeof(a_zeroed[0]); i++) + sum += a_zeroed[i]; + return sum; +} + +void a_zeroed_fill(int value) { + for (size_t i = 0; i < sizeof(a_zeroed) / sizeof(a_zeroed[0]); i++) + a_zeroed[i] = value; +} diff --git a/test/src/shared/tls_a.h b/test/src/shared/tls_a.h new file mode 100644 index 000000000..f5a694007 --- /dev/null +++ b/test/src/shared/tls_a.h @@ -0,0 +1,16 @@ +#ifndef WASI_LIBC_TEST_TLS_A_H +#define WASI_LIBC_TEST_TLS_A_H + +#define A_COUNTER_INIT 0x0a0a0a0a +#define A_NAME_SIZE 32 +#define A_NAME_INIT "tls_a" + +int a_counter_get(void); +void a_counter_set(int value); +void *a_counter_addr(void); +const char *a_name_get(void); +void a_name_set(const char *value); +int a_zeroed_sum(void); +void a_zeroed_fill(int value); + +#endif diff --git a/test/src/shared/tls_b.c b/test/src/shared/tls_b.c new file mode 100644 index 000000000..c317eac9a --- /dev/null +++ b/test/src/shared/tls_b.c @@ -0,0 +1,22 @@ +#include "tls_b.h" +#include "tls_a.h" + +_Thread_local long long b_counter = B_COUNTER_INIT; + +_Alignas(B_ALIGN) _Thread_local char b_aligned[B_ALIGN]; + +_Thread_local int b_blob[B_BLOB_SIZE]; + +long long b_counter_get(void) { return b_counter; } + +void b_counter_set(long long value) { b_counter = value; } + +void *b_counter_addr(void) { return &b_counter; } + +void *b_aligned_addr(void) { return b_aligned; } + +int b_blob_get(size_t index) { return b_blob[index]; } + +void b_blob_set(size_t index, int value) { b_blob[index] = value; } + +int b_read_a_counter(void) { return a_counter_get(); } diff --git a/test/src/shared/tls_b.h b/test/src/shared/tls_b.h new file mode 100644 index 000000000..2c563b7a7 --- /dev/null +++ b/test/src/shared/tls_b.h @@ -0,0 +1,18 @@ +#ifndef WASI_LIBC_TEST_TLS_B_H +#define WASI_LIBC_TEST_TLS_B_H + +#include + +#define B_COUNTER_INIT 0x0b0b0b0b +#define B_ALIGN 64 +#define B_BLOB_SIZE 24 + +long long b_counter_get(void); +void b_counter_set(long long value); +void *b_counter_addr(void); +void *b_aligned_addr(void); +int b_blob_get(size_t index); +void b_blob_set(size_t index, int value); +int b_read_a_counter(void); + +#endif diff --git a/test/src/shared/tls_none.c b/test/src/shared/tls_none.c new file mode 100644 index 000000000..290497f1f --- /dev/null +++ b/test/src/shared/tls_none.c @@ -0,0 +1,9 @@ +#include "tls_none.h" + +static int state = 0; + +int none_add(int a, int b) { return a + b; } + +int none_state_get(void) { return state; } + +void none_state_set(int value) { state = value; } diff --git a/test/src/shared/tls_none.h b/test/src/shared/tls_none.h new file mode 100644 index 000000000..1534845d5 --- /dev/null +++ b/test/src/shared/tls_none.h @@ -0,0 +1,8 @@ +#ifndef WASI_LIBC_TEST_TLS_NONE_H +#define WASI_LIBC_TEST_TLS_NONE_H + +int none_add(int a, int b); +int none_state_get(void); +void none_state_set(int value); + +#endif