From 842e8a6796444abb25a450c2432359d944cc70ca Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Mon, 11 May 2026 16:35:24 +0200 Subject: [PATCH] LibWasm+LibWeb: Add per-module wasm compile stats --- .../AbstractMachine/AbstractMachine.cpp | 57 +++++++++++++++++++ .../LibWasm/AbstractMachine/Validator.cpp | 24 ++++++++ Libraries/LibWasm/Types.h | 23 ++++++++ Libraries/LibWeb/WebAssembly/WebAssembly.cpp | 18 +++++- Libraries/LibWebView/Application.cpp | 1 + Libraries/LibWebView/Menu.h | 1 + Services/WebContent/CMakeLists.txt | 2 +- Services/WebContent/ConnectionFromClient.cpp | 6 ++ 8 files changed, 130 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp b/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp index 396d8957c4..32fd8a7c08 100644 --- a/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp +++ b/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp @@ -16,6 +16,63 @@ namespace Wasm { +static Vector s_module_stats; + +void record_module_stats(ModuleStats stats) +{ + s_module_stats.append(move(stats)); +} + +void dump_module_stats() +{ + if (s_module_stats.is_empty()) { + warnln("wasm-stats: no modules compiled yet"); + return; + } + + warnln("wasm-stats: {} module(s) compiled", s_module_stats.size()); + warnln("wasm-stats: hash input KiB parse ms validate ms cl ms cl blob KiB funcs cache"); + + AK::Duration total_parse; + AK::Duration total_validate; + AK::Duration total_cranelift; + size_t total_input = 0; + size_t total_blob = 0; + size_t total_hits = 0; + + for (auto const& s : s_module_stats) { + StringBuilder hash_prefix; + for (size_t i = 0; i < 4; ++i) + hash_prefix.appendff("{:02x}", s.wasm_hash[i]); + + warnln("wasm-stats: {} {:>9} {:>8} {:>11} {:>5} {:>11} {:>5} {}", + hash_prefix.to_byte_string(), + s.input_size_bytes / 1024, + s.parse_time.to_milliseconds(), + s.validate_time.to_milliseconds(), + s.cranelift_time.to_milliseconds(), + s.cranelift_blob_size_bytes / 1024, + s.function_count, + s.cache_hit ? "HIT" : "miss"); + + total_parse = total_parse + s.parse_time; + total_validate = total_validate + s.validate_time; + total_cranelift = total_cranelift + s.cranelift_time; + total_input += s.input_size_bytes; + total_blob += s.cranelift_blob_size_bytes; + if (s.cache_hit) + ++total_hits; + } + + warnln("wasm-stats: ---- {:>9} {:>8} {:>11} {:>5} {:>11} hits={}", + total_input / 1024, + total_parse.to_milliseconds(), + total_validate.to_milliseconds(), + total_cranelift.to_milliseconds(), + total_blob / 1024, + total_hits); +} + MemoryBuffer::~MemoryBuffer() { clear(); diff --git a/Libraries/LibWasm/AbstractMachine/Validator.cpp b/Libraries/LibWasm/AbstractMachine/Validator.cpp index f886e39bcc..6924266750 100644 --- a/Libraries/LibWasm/AbstractMachine/Validator.cpp +++ b/Libraries/LibWasm/AbstractMachine/Validator.cpp @@ -281,16 +281,24 @@ ErrorOr Validator::validate(CodeSection const& section) begin_cranelift_cache_capture(); capturing = true; } + if (m_cache_config->out_cache_hit) + *m_cache_config->out_cache_hit = installing; } ScopeGuard cleanup = [&] { + auto cranelift_start = MonotonicTime::now(); flush_cranelift_batch(); + auto cranelift_duration = MonotonicTime::now() - cranelift_start; + if (installing) abort_cranelift_cache_install(); + + size_t produced_blob_size = 0; if (capturing) { if (validation_succeeded) { auto hash = ReadonlyBytes { m_cache_config->wasm_hash.data(), 32 }; if (auto blob = serialize_cranelift_cache_blob(hash); blob.has_value() && m_cache_config->on_compiled) { + produced_blob_size = blob->size(); m_cache_config->on_compiled(blob.release_value()); } else { abort_cranelift_cache_capture(); @@ -299,6 +307,22 @@ ErrorOr Validator::validate(CodeSection const& section) abort_cranelift_cache_capture(); } } + + if (m_cache_config.has_value()) { + if (m_cache_config->out_cranelift_time) + *m_cache_config->out_cranelift_time = cranelift_duration; + if (m_cache_config->out_cranelift_blob_size_bytes) + *m_cache_config->out_cranelift_blob_size_bytes = produced_blob_size; + if (m_cache_config->out_function_count) { + size_t count = 0; + for (auto& entry : section.functions()) { + if (entry.func().body().compiled_instructions.cranelift_compiled) + ++count; + } + *m_cache_config->out_function_count = count; + } + } + set_cranelift_active_function_index(NumericLimits::max()); }; diff --git a/Libraries/LibWasm/Types.h b/Libraries/LibWasm/Types.h index 17602847ae..80405b1b92 100644 --- a/Libraries/LibWasm/Types.h +++ b/Libraries/LibWasm/Types.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -1555,12 +1556,34 @@ void flush_cranelift_batch(); // to install it before falling through to cranelift. // - `on_compiled` is invoked exactly once if a fresh blob was produced; never // invoked on a cache hit nor when no cranelift output was captured. +// - The `out_*` fields, if non-null, receive measurements taken during +// validate(CodeSection). Used by the LibWeb side to populate ModuleStats. struct CompileCacheConfig { Array wasm_hash {}; ReadonlyBytes existing_blob; AK::Function on_compiled; + + AK::Duration* out_cranelift_time { nullptr }; + size_t* out_function_count { nullptr }; + size_t* out_cranelift_blob_size_bytes { nullptr }; + bool* out_cache_hit { nullptr }; }; +// Lightweight per-module compile stats accumulator. Exposed to embedders via record_module_stats() below. +struct ModuleStats { + Array wasm_hash {}; + size_t input_size_bytes { 0 }; + AK::Duration parse_time; + AK::Duration validate_time; + AK::Duration cranelift_time; + size_t cranelift_blob_size_bytes { 0 }; + size_t function_count { 0 }; + bool cache_hit { false }; +}; + +WASM_API void record_module_stats(ModuleStats); +WASM_API void dump_module_stats(); + // Cranelift disk-cache plumbing. Validator drives these around CodeSection validation: // 1. set_cranelift_active_function_index() before each function so cache-hit installs // and post-compile capture know which function they're talking about. diff --git a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp index 42d3e58bbc..b0668a2113 100644 --- a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp +++ b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp @@ -432,8 +432,13 @@ JS::ThrowCompletionOr> compile_a_webass { TRY(host_ensure_can_compile_wasm_bytes(vm)); + Wasm::ModuleStats stats; + stats.input_size_bytes = data.size(); + + auto parse_start = MonotonicTime::now(); FixedMemoryStream stream { data.bytes() }; auto module_result = Wasm::Module::parse(stream); + stats.parse_time = MonotonicTime::now() - parse_start; if (module_result.is_error()) { return vm.throw_completion(Wasm::parse_error_to_byte_string(module_result.error())); } @@ -447,6 +452,7 @@ JS::ThrowCompletionOr> compile_a_webass Optional wasm_cache_config; if (ResourceLoader::is_initialized() && ResourceLoader::the().request_client()) { auto digest = ::Crypto::Hash::SHA256::hash(data.data(), data.size()); + __builtin_memcpy(stats.wasm_hash.data(), digest.bytes().data(), 32); StringBuilder hex_builder; for (auto byte : digest.bytes()) @@ -476,12 +482,22 @@ JS::ThrowCompletionOr> compile_a_webass url, method, OptionalNone {}, 0u, HTTP::CacheEntryAssociatedData::WebAssemblyCompiledCode, blob.bytes()); }; + + config.out_cranelift_time = &stats.cranelift_time; + config.out_function_count = &stats.function_count; + config.out_cranelift_blob_size_bytes = &stats.cranelift_blob_size_bytes; + config.out_cache_hit = &stats.cache_hit; wasm_cache_config = move(config); } } auto& cache = get_cache(*vm.current_realm()); - if (auto validation_result = cache.abstract_machine().validate(module_result.value(), move(wasm_cache_config)); validation_result.is_error()) { + auto validate_start = MonotonicTime::now(); + auto validation_result = cache.abstract_machine().validate(module_result.value(), move(wasm_cache_config)); + stats.validate_time = MonotonicTime::now() - validate_start; + Wasm::record_module_stats(stats); + + if (validation_result.is_error()) { return vm.throw_completion(validation_result.error().error_string); } auto compiled_module = make_ref_counted(module_result.release_value()); diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index bb775444d4..d28ea44217 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -1455,6 +1455,7 @@ void Application::initialize_actions() m_debug_menu->add_action(Action::create("Dump CSS Errors"sv, ActionID::DumpCSSErrors, debug_request("dump-all-css-errors"sv))); m_debug_menu->add_action(Action::create("Dump Cookies"sv, ActionID::DumpCookies, [this]() { m_cookie_jar->dump_cookies(); })); m_debug_menu->add_action(Action::create("Dump Local Storage"sv, ActionID::DumpLocalStorage, debug_request("dump-local-storage"sv))); + m_debug_menu->add_action(Action::create("Dump WASM Stats"sv, ActionID::DumpWasmStats, debug_request("dump-wasm-stats"sv))); m_debug_menu->add_action(Action::create("Dump GC graph"sv, ActionID::DumpGCGraph, [this]() { if (auto view = active_web_view(); view.has_value()) { auto gc_graph_path = view->dump_gc_graph(); diff --git a/Libraries/LibWebView/Menu.h b/Libraries/LibWebView/Menu.h index ab6397cdb6..6fe2187c74 100644 --- a/Libraries/LibWebView/Menu.h +++ b/Libraries/LibWebView/Menu.h @@ -98,6 +98,7 @@ enum class ActionID { DumpCookies, DumpLocalStorage, DumpGCGraph, + DumpWasmStats, ShowLineBoxBorders, CollectGarbage, SpoofUserAgent, diff --git a/Services/WebContent/CMakeLists.txt b/Services/WebContent/CMakeLists.txt index be2dd1256d..835b3865f0 100644 --- a/Services/WebContent/CMakeLists.txt +++ b/Services/WebContent/CMakeLists.txt @@ -31,7 +31,7 @@ target_include_directories(webcontentservice PUBLIC $) target_include_directories(webcontentservice PUBLIC $) -target_link_libraries(webcontentservice PUBLIC LibCore LibCrypto LibFileSystem LibGfx LibHTTP LibIPC LibJS LibMain LibMedia LibWeb LibWebSocket LibRequests LibWebView LibImageDecoderClient LibGC) +target_link_libraries(webcontentservice PUBLIC LibCore LibCrypto LibFileSystem LibGfx LibHTTP LibIPC LibJS LibMain LibMedia LibWasm LibWeb LibWebSocket LibRequests LibWebView LibImageDecoderClient LibGC) target_link_libraries(webcontentservice PRIVATE OpenSSL::Crypto OpenSSL::SSL) target_link_libraries(webcontentservice PRIVATE SDL3::SDL3) diff --git a/Services/WebContent/ConnectionFromClient.cpp b/Services/WebContent/ConnectionFromClient.cpp index 50f7ee0e63..7de5780476 100644 --- a/Services/WebContent/ConnectionFromClient.cpp +++ b/Services/WebContent/ConnectionFromClient.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -428,6 +429,11 @@ void ConnectionFromClient::debug_request(u64 page_id, ByteString request, ByteSt return; } + if (request == "dump-wasm-stats") { + Wasm::dump_module_stats(); + return; + } + if (request == "collect-garbage") { // NOTE: We use deferred_invoke here to ensure that GC runs with as little on the stack as possible. Core::deferred_invoke([] {