From 8a720a9be97c9ed73c64e7d80fce1a6ee0ba5608 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 9 Jun 2026 13:54:57 -0400 Subject: [PATCH] wasm: lazily initialize tls.std, add configuration macro for assert logging sp_assert_f unconditionally logged a nice message when triggered. This, however, forced WASM builds to pull in fd_write. In the browser, this forces you to shim fd_write with no recourse, even if you don't want the logging. Added a macro to configure what happens on assert (nothing, trap only, trap and log). I decided against a higher level configuration (like SP_NO_IO); it's a little unintuitive that, if you were to pull in sp.h for such a WASM thing, that it defaults to wanting fd_write and the configuration to turn that off is hidden in the configuration for assert. Fairly unintuitive. But sp_assert_f() is legitimately the only place that the library writes output. It's a standard library. It SHOULDN'T log during normal use. Presenting a macro framed as "make this library log" is equally confusing. I also considered defaulting to trap only on WASM, but that degrades the equally common case of running a WASM program inside a preview1 enabled environment. --- sp.h | 85 ++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/sp.h b/sp.h index ca385ae..b5bb726 100644 --- a/sp.h +++ b/sp.h @@ -192,6 +192,17 @@ #define SP_CPP #endif + +// █████████ ███████ ██████ █████ ███████████ █████ █████████ +// ███░░░░░███ ███░░░░░███ ░░██████ ░░███ ░░███░░░░░░█░░███ ███░░░░░███ +// ███ ░░░ ███ ░░███ ░███░███ ░███ ░███ █ ░ ░███ ███ ░░░ +// ░███ ░███ ░███ ░███░░███░███ ░███████ ░███ ░███ +// ░███ ░███ ░███ ░███ ░░██████ ░███░░░█ ░███ ░███ █████ +// ░░███ ███░░███ ███ ░███ ░░█████ ░███ ░ ░███ ░░███ ░░███ +// ░░█████████ ░░░███████░ █████ ░░█████ █████ █████ ░░█████████ +// ░░░░░░░░░ ░░░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░░░░░ +// @config + ///////////// // LINKAGE // ///////////// @@ -229,6 +240,20 @@ #endif #endif +//////////// +// ASSERT // +//////////// + +#define SP_ASSERT_NONE 0 +#define SP_ASSERT_TRAP 1 +#define SP_ASSERT_LOG 2 + +#define SP_ASSERT_ENABLED(cap) (SP_ASSERT_LEVEL >= (cap)) + +#if !defined(SP_ASSERT_LEVEL) + #define SP_ASSERT_LEVEL SP_ASSERT_LOG +#endif + // ██████ ██████ █████████ █████████ ███████████ ███████ █████████ // ░░██████ ██████ ███░░░░░███ ███░░░░░███░░███░░░░░███ ███░░░░░███ ███░░░░░███ @@ -323,7 +348,6 @@ #define SP_FALLTHROUGH() ((void)0) #endif - ///////////// // SP_RVAL // ///////////// @@ -342,9 +366,9 @@ #define SP_THREAD_LOCAL _Thread_local #endif -///////////////////// +/////////////////// // SP_*_EXTERN_C // -///////////////////// +/////////////////// #ifdef SP_CPP #define SP_EXTERN_C extern "C" #define SP_BEGIN_EXTERN_C() extern "C" { @@ -356,7 +380,7 @@ #endif ///////////// -// sp_zero // +// SP_ZERO // ///////////// #ifdef SP_CPP #define sp_zero {} @@ -584,7 +608,7 @@ SP_BEGIN_EXTERN_C() #if defined(SP_FREESTANDING) -#elif defined(SP_LINUX) // @preprocessor +#elif defined(SP_LINUX) #include #include #include @@ -3629,6 +3653,8 @@ SP_PRIVATE void sp_tls_new(sp_tls_key_t* key, sp_tls_deinit_fn_t fn); SP_PRIVATE void* sp_tls_get(sp_tls_key_t key); SP_PRIVATE void sp_tls_set(sp_tls_key_t key, void* data); SP_PRIVATE void sp_tls_once(sp_tls_once_t* once, sp_tls_once_fn_t); +SP_PRIVATE sp_io_writer_t* sp_tls_std_out(sp_tls_rt_t* tls); +SP_PRIVATE sp_io_writer_t* sp_tls_std_err(sp_tls_rt_t* tls); // @io SP_IMP sp_err_t sp_io_file_reader_read(sp_io_reader_t* reader, void* ptr, u64 size, u64* bytes_read); @@ -7847,8 +7873,8 @@ void sp_rt_init() { void sp_tls_rt_deinit(void* ptr) { if (!ptr) return; sp_tls_rt_t* tls = (sp_tls_rt_t*)ptr; - sp_mem_allocator_free(tls->mem, tls->std.out); - sp_mem_allocator_free(tls->mem, tls->std.err); + if (tls->std.out) sp_mem_allocator_free(tls->mem, tls->std.out); + if (tls->std.err) sp_mem_allocator_free(tls->mem, tls->std.err); sp_str_ht_free(tls->format.directives); sp_carr_for(tls->scratch, it) { sp_mem_arena_destroy(tls->scratch[it]); @@ -7871,10 +7897,9 @@ sp_tls_rt_t* sp_tls_rt_get() { sp_carr_for(tls->scratch, it) { tls->scratch[it] = sp_mem_arena_new(tls->mem); } - tls->std.out = sp_alloc_type(tls->mem, sp_io_stream_writer_t); - tls->std.err = sp_alloc_type(tls->mem, sp_io_stream_writer_t); - sp_io_stream_writer_from_fd(tls->std.out, sp_sys_stdout, SP_IO_CLOSE_MODE_NONE); - sp_io_stream_writer_from_fd(tls->std.err, sp_sys_stderr, SP_IO_CLOSE_MODE_NONE); + // std.out/std.err are wired lazily (see sp_tls_std_out/_err). Wiring them here + // would take the address of sp_io_stream_writer_write in code that sp_main always + // reaches, which on WASM forces a fd_write import even for programs that never write. sp_str_ht_init(tls->mem, tls->format.directives); sp_fmt_register_builtins(); sp_sys_tls_init(tls); @@ -7882,6 +7907,22 @@ sp_tls_rt_t* sp_tls_rt_get() { return tls; } +sp_io_writer_t* sp_tls_std_out(sp_tls_rt_t* tls) { + if (!tls->std.out) { + tls->std.out = sp_alloc_type(tls->mem, sp_io_stream_writer_t); + sp_io_stream_writer_from_fd(tls->std.out, sp_sys_stdout, SP_IO_CLOSE_MODE_NONE); + } + return &tls->std.out->base; +} + +sp_io_writer_t* sp_tls_std_err(sp_tls_rt_t* tls) { + if (!tls->std.err) { + tls->std.err = sp_alloc_type(tls->mem, sp_io_stream_writer_t); + sp_io_stream_writer_from_fd(tls->std.err, sp_sys_stderr, SP_IO_CLOSE_MODE_NONE); + } + return &tls->std.err->base; +} + #if defined(SP_FREESTANDING) void sp_tls_new(sp_tls_key_t* key, sp_tls_deinit_fn_t on_deinit) { @@ -9487,6 +9528,7 @@ s32 sp_sys_fs_it_next(sp_sys_fs_it_t* it, sp_sys_fs_entry_t* out) { void sp_assert_f(sp_str_t file, sp_str_t line, sp_str_t func, sp_str_t expr, bool cond) { if (cond) return; +#if SP_ASSERT_ENABLED(SP_ASSERT_LOG) sp_io_stream_writer_t io = sp_zero; sp_io_stream_writer_from_fd(&io, sp_sys_stderr, SP_IO_CLOSE_MODE_NONE); sp_fmt_io( @@ -9500,8 +9542,11 @@ void sp_assert_f(sp_str_t file, sp_str_t line, sp_str_t func, sp_str_t expr, boo sp_fmt_str(expr) ); sp_io_write_cstr(&io.base, "\n", SP_NULLPTR); +#endif +#if SP_ASSERT_ENABLED(SP_ASSERT_TRAP) sp_sys_assert(cond); +#endif } /////////// @@ -14538,34 +14583,34 @@ void sp_log(const c8* fmt, ...) { sp_tls_rt_t* tls = sp_tls_rt_get(); va_list args; va_start(args, fmt); - sp_fmt_io_v(&tls->std.out->base, sp_str_view(fmt), args); + sp_fmt_io_v(sp_tls_std_out(tls), sp_str_view(fmt), args); va_end(args); - sp_io_write_cstr(&tls->std.out->base, "\n", SP_NULLPTR); + sp_io_write_cstr(sp_tls_std_out(tls), "\n", SP_NULLPTR); } void sp_log_str(sp_str_t fmt, ...) { sp_tls_rt_t* tls = sp_tls_rt_get(); va_list args; va_start(args, fmt); - sp_fmt_io_v(&tls->std.out->base, fmt, args); + sp_fmt_io_v(sp_tls_std_out(tls), fmt, args); va_end(args); - sp_io_write_cstr(&tls->std.out->base, "\n", SP_NULLPTR); + sp_io_write_cstr(sp_tls_std_out(tls), "\n", SP_NULLPTR); } void sp_log_err(const c8* fmt, ...) { sp_tls_rt_t* tls = sp_tls_rt_get(); va_list args; va_start(args, fmt); - sp_fmt_io_v(&tls->std.out->base, sp_str_view(fmt), args); + sp_fmt_io_v(sp_tls_std_out(tls), sp_str_view(fmt), args); va_end(args); - sp_io_write_cstr(&tls->std.out->base, "\n", SP_NULLPTR); + sp_io_write_cstr(sp_tls_std_out(tls), "\n", SP_NULLPTR); } void sp_print(const c8* fmt, ...) { sp_tls_rt_t* tls = sp_tls_rt_get(); va_list args; va_start(args, fmt); - sp_fmt_io_v(&tls->std.out->base, sp_str_view(fmt), args); + sp_fmt_io_v(sp_tls_std_out(tls), sp_str_view(fmt), args); va_end(args); } @@ -14573,7 +14618,7 @@ void sp_print_str(sp_str_t fmt, ...) { sp_tls_rt_t* tls = sp_tls_rt_get(); va_list args; va_start(args, fmt); - sp_fmt_io_v(&tls->std.out->base, fmt, args); + sp_fmt_io_v(sp_tls_std_out(tls), fmt, args); va_end(args); } @@ -14581,7 +14626,7 @@ void sp_print_err(const c8* fmt, ...) { sp_tls_rt_t* tls = sp_tls_rt_get(); va_list args; va_start(args, fmt); - sp_fmt_io_v(&tls->std.err->base, sp_str_view(fmt), args); + sp_fmt_io_v(sp_tls_std_err(tls), sp_str_view(fmt), args); va_end(args); }