From 4ef724bbfffab1c7761b2674c483a8e7d7c840b8 Mon Sep 17 00:00:00 2001 From: "liang.he" Date: Sun, 7 Apr 2024 15:04:35 +0800 Subject: [PATCH] Enhance wasm loading with LoadArgs and support module names (#3265) - Add new API wasm_runtime_load_ex() in wasm_export.h and wasm_module_new_ex in wasm_c_api.h - Put aot_create_perf_map() into a separated file aot_perf_map.c - In perf.map, function names include user specified module name - Enhance the script to help flamegraph generations --- core/iwasm/aot/aot_loader.c | 116 +- core/iwasm/aot/aot_perf_map.c | 120 ++ core/iwasm/aot/aot_perf_map.h | 15 + core/iwasm/aot/aot_runtime.h | 4 +- core/iwasm/common/wasm_c_api.c | 15 +- core/iwasm/common/wasm_runtime_common.c | 34 +- core/iwasm/compilation/aot_llvm.c | 2 +- core/iwasm/include/wasm_c_api.h | 11 + core/iwasm/include/wasm_export.h | 15 + core/iwasm/interpreter/wasm_loader.c | 10 +- core/iwasm/interpreter/wasm_loader.h | 2 +- core/iwasm/interpreter/wasm_mini_loader.c | 10 +- core/iwasm/interpreter/wasm_runtime.c | 4 +- core/iwasm/interpreter/wasm_runtime.h | 2 +- samples/linux-perf/CMakeLists.txt | 63 + samples/linux-perf/README.md | 90 ++ samples/linux-perf/cmake/FindWAMRC.cmake | 14 + samples/linux-perf/cmake/FindWASISDK.cmake | 23 + samples/linux-perf/host/demo.c | 198 +++ samples/linux-perf/pics/perf.ackermann.svg | 1349 +++++++++++++++++ samples/linux-perf/pics/perf.fib.svg | 605 ++++++++ samples/linux-perf/pics/perf.png | Bin 0 -> 92293 bytes samples/linux-perf/wasm/CMakeLists.txt | 42 + samples/linux-perf/wasm/ackermann.c | 38 + samples/linux-perf/wasm/fib.c | 32 + test-tools/flame-graph-helper/.gitignore | 2 + .../flame-graph-helper/process_folded_data.py | 325 ++++ .../trans_wasm_func_name.py | 213 --- 28 files changed, 3008 insertions(+), 346 deletions(-) create mode 100644 core/iwasm/aot/aot_perf_map.c create mode 100644 core/iwasm/aot/aot_perf_map.h create mode 100644 samples/linux-perf/CMakeLists.txt create mode 100644 samples/linux-perf/README.md create mode 100644 samples/linux-perf/cmake/FindWAMRC.cmake create mode 100644 samples/linux-perf/cmake/FindWASISDK.cmake create mode 100644 samples/linux-perf/host/demo.c create mode 100644 samples/linux-perf/pics/perf.ackermann.svg create mode 100644 samples/linux-perf/pics/perf.fib.svg create mode 100755 samples/linux-perf/pics/perf.png create mode 100644 samples/linux-perf/wasm/CMakeLists.txt create mode 100644 samples/linux-perf/wasm/ackermann.c create mode 100644 samples/linux-perf/wasm/fib.c create mode 100644 test-tools/flame-graph-helper/.gitignore create mode 100644 test-tools/flame-graph-helper/process_folded_data.py delete mode 100644 test-tools/trans-jitted-func-name/trans_wasm_func_name.py diff --git a/core/iwasm/aot/aot_loader.c b/core/iwasm/aot/aot_loader.c index 9789b17445..df487039cb 100644 --- a/core/iwasm/aot/aot_loader.c +++ b/core/iwasm/aot/aot_loader.c @@ -16,6 +16,10 @@ #include "debug/jit_debug.h" #endif +#if WASM_ENABLE_LINUX_PERF != 0 +#include "aot_perf_map.h" +#endif + #define YMM_PLT_PREFIX "__ymm@" #define XMM_PLT_PREFIX "__xmm@" #define REAL_PLT_PREFIX "__real@" @@ -3601,104 +3605,6 @@ load_relocation_section(const uint8 *buf, const uint8 *buf_end, return ret; } -#if WASM_ENABLE_LINUX_PERF != 0 -struct func_info { - uint32 idx; - void *ptr; -}; - -static uint32 -get_func_size(const AOTModule *module, struct func_info *sorted_func_ptrs, - uint32 idx) -{ - uint32 func_sz; - - if (idx == module->func_count - 1) - func_sz = (uintptr_t)module->code + module->code_size - - (uintptr_t)(sorted_func_ptrs[idx].ptr); - else - func_sz = (uintptr_t)(sorted_func_ptrs[idx + 1].ptr) - - (uintptr_t)(sorted_func_ptrs[idx].ptr); - - return func_sz; -} - -static int -compare_func_ptrs(const void *f1, const void *f2) -{ - return (intptr_t)((struct func_info *)f1)->ptr - - (intptr_t)((struct func_info *)f2)->ptr; -} - -static struct func_info * -sort_func_ptrs(const AOTModule *module, char *error_buf, uint32 error_buf_size) -{ - uint64 content_len; - struct func_info *sorted_func_ptrs; - unsigned i; - - content_len = (uint64)sizeof(struct func_info) * module->func_count; - sorted_func_ptrs = loader_malloc(content_len, error_buf, error_buf_size); - if (!sorted_func_ptrs) - return NULL; - - for (i = 0; i < module->func_count; i++) { - sorted_func_ptrs[i].idx = i; - sorted_func_ptrs[i].ptr = module->func_ptrs[i]; - } - - qsort(sorted_func_ptrs, module->func_count, sizeof(struct func_info), - compare_func_ptrs); - - return sorted_func_ptrs; -} - -static bool -create_perf_map(const AOTModule *module, char *error_buf, uint32 error_buf_size) -{ - struct func_info *sorted_func_ptrs = NULL; - char perf_map_info[128] = { 0 }; - FILE *perf_map = NULL; - uint32 i; - pid_t pid = getpid(); - bool ret = false; - - sorted_func_ptrs = sort_func_ptrs(module, error_buf, error_buf_size); - if (!sorted_func_ptrs) - goto quit; - - snprintf(perf_map_info, 128, "/tmp/perf-%d.map", pid); - perf_map = fopen(perf_map_info, "w"); - if (!perf_map) { - LOG_WARNING("warning: can't create /tmp/perf-%d.map, because %s", pid, - strerror(errno)); - goto quit; - } - - for (i = 0; i < module->func_count; i++) { - memset(perf_map_info, 0, 128); - snprintf(perf_map_info, 128, "%lx %x aot_func#%u\n", - (uintptr_t)sorted_func_ptrs[i].ptr, - get_func_size(module, sorted_func_ptrs, i), - sorted_func_ptrs[i].idx); - - fwrite(perf_map_info, 1, strlen(perf_map_info), perf_map); - } - - LOG_VERBOSE("generate /tmp/perf-%d.map", pid); - ret = true; - -quit: - if (sorted_func_ptrs) - free(sorted_func_ptrs); - - if (perf_map) - fclose(perf_map); - - return ret; -} -#endif /* WASM_ENABLE_LINUX_PERF != 0*/ - static bool load_from_sections(AOTModule *module, AOTSection *sections, bool is_load_from_file_buf, char *error_buf, @@ -3889,7 +3795,7 @@ load_from_sections(AOTModule *module, AOTSection *sections, } static AOTModule * -create_module(char *error_buf, uint32 error_buf_size) +create_module(char *name, char *error_buf, uint32 error_buf_size) { AOTModule *module = loader_malloc(sizeof(AOTModule), error_buf, error_buf_size); @@ -3901,7 +3807,7 @@ create_module(char *error_buf, uint32 error_buf_size) module->module_type = Wasm_Module_AoT; - module->name = ""; + module->name = name; #if WASM_ENABLE_MULTI_MODULE != 0 module->import_module_list = &module->import_module_list_head; @@ -3937,7 +3843,7 @@ AOTModule * aot_load_from_sections(AOTSection *section_list, char *error_buf, uint32 error_buf_size) { - AOTModule *module = create_module(error_buf, error_buf_size); + AOTModule *module = create_module("", error_buf, error_buf_size); if (!module) return NULL; @@ -4183,7 +4089,7 @@ load(const uint8 *buf, uint32 size, AOTModule *module, char *error_buf, #if WASM_ENABLE_LINUX_PERF != 0 if (wasm_runtime_get_linux_perf()) - if (!create_perf_map(module, error_buf, error_buf_size)) + if (!aot_create_perf_map(module, error_buf, error_buf_size)) goto fail; #endif @@ -4193,10 +4099,10 @@ load(const uint8 *buf, uint32 size, AOTModule *module, char *error_buf, } AOTModule * -aot_load_from_aot_file(const uint8 *buf, uint32 size, char *error_buf, - uint32 error_buf_size) +aot_load_from_aot_file(const uint8 *buf, uint32 size, const LoadArgs *args, + char *error_buf, uint32 error_buf_size) { - AOTModule *module = create_module(error_buf, error_buf_size); + AOTModule *module = create_module(args->name, error_buf, error_buf_size); if (!module) return NULL; diff --git a/core/iwasm/aot/aot_perf_map.c b/core/iwasm/aot/aot_perf_map.c new file mode 100644 index 0000000000..22700dcdd6 --- /dev/null +++ b/core/iwasm/aot/aot_perf_map.c @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2019 Intel Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include "aot_perf_map.h" +#include "bh_log.h" +#include "bh_platform.h" + +#if WASM_ENABLE_LINUX_PERF != 0 +struct func_info { + uint32 idx; + void *ptr; +}; + +static uint32 +get_func_size(const AOTModule *module, struct func_info *sorted_func_ptrs, + uint32 idx) +{ + uint32 func_sz; + + if (idx == module->func_count - 1) + func_sz = (uintptr_t)module->code + module->code_size + - (uintptr_t)(sorted_func_ptrs[idx].ptr); + else + func_sz = (uintptr_t)(sorted_func_ptrs[idx + 1].ptr) + - (uintptr_t)(sorted_func_ptrs[idx].ptr); + + return func_sz; +} + +static int +compare_func_ptrs(const void *f1, const void *f2) +{ + return (intptr_t)((struct func_info *)f1)->ptr + - (intptr_t)((struct func_info *)f2)->ptr; +} + +static struct func_info * +sort_func_ptrs(const AOTModule *module, char *error_buf, uint32 error_buf_size) +{ + uint64 content_len; + struct func_info *sorted_func_ptrs; + unsigned i; + + content_len = (uint64)sizeof(struct func_info) * module->func_count; + sorted_func_ptrs = wasm_runtime_malloc(content_len); + if (!sorted_func_ptrs) { + snprintf(error_buf, error_buf_size, + "allocate memory failed when creating perf map"); + return NULL; + } + + for (i = 0; i < module->func_count; i++) { + sorted_func_ptrs[i].idx = i; + sorted_func_ptrs[i].ptr = module->func_ptrs[i]; + } + + qsort(sorted_func_ptrs, module->func_count, sizeof(struct func_info), + compare_func_ptrs); + + return sorted_func_ptrs; +} + +bool +aot_create_perf_map(const AOTModule *module, char *error_buf, + uint32 error_buf_size) +{ + struct func_info *sorted_func_ptrs = NULL; + char perf_map_path[64] = { 0 }; + char perf_map_info[128] = { 0 }; + FILE *perf_map = NULL; + uint32 i; + pid_t pid = getpid(); + bool ret = false; + + sorted_func_ptrs = sort_func_ptrs(module, error_buf, error_buf_size); + if (!sorted_func_ptrs) + goto quit; + + snprintf(perf_map_path, sizeof(perf_map_path) - 1, "/tmp/perf-%d.map", pid); + perf_map = fopen(perf_map_path, "a"); + if (!perf_map) { + LOG_WARNING("warning: can't create /tmp/perf-%d.map, because %s", pid, + strerror(errno)); + goto quit; + } + + const char *module_name = aot_get_module_name((AOTModule *)module); + for (i = 0; i < module->func_count; i++) { + memset(perf_map_info, 0, 128); + if (strlen(module_name) > 0) + snprintf(perf_map_info, 128, "%lx %x [%s]#aot_func#%u\n", + (uintptr_t)sorted_func_ptrs[i].ptr, + get_func_size(module, sorted_func_ptrs, i), module_name, + sorted_func_ptrs[i].idx); + else + snprintf(perf_map_info, 128, "%lx %x aot_func#%u\n", + (uintptr_t)sorted_func_ptrs[i].ptr, + get_func_size(module, sorted_func_ptrs, i), + sorted_func_ptrs[i].idx); + + /* fwrite() is thread safe */ + fwrite(perf_map_info, 1, strlen(perf_map_info), perf_map); + } + + LOG_VERBOSE("write map information from %s into /tmp/perf-%d.map", + module_name, pid); + ret = true; + +quit: + if (sorted_func_ptrs) + wasm_runtime_free(sorted_func_ptrs); + + if (perf_map) + fclose(perf_map); + + return ret; +} +#endif /* WASM_ENABLE_LINUX_PERF != 0 */ \ No newline at end of file diff --git a/core/iwasm/aot/aot_perf_map.h b/core/iwasm/aot/aot_perf_map.h new file mode 100644 index 0000000000..3e6583c5cd --- /dev/null +++ b/core/iwasm/aot/aot_perf_map.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2019 Intel Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#ifndef _AOT_PERF_MAP_H_ +#define _AOT_PERF_MAP_H_ + +#include "aot_runtime.h" + +bool +aot_create_perf_map(const AOTModule *module, char *error_buf, + uint32 error_buf_size); + +#endif /* _AOT_PERF_MAP_H_ */ \ No newline at end of file diff --git a/core/iwasm/aot/aot_runtime.h b/core/iwasm/aot/aot_runtime.h index 519c1edc19..2d30134670 100644 --- a/core/iwasm/aot/aot_runtime.h +++ b/core/iwasm/aot/aot_runtime.h @@ -444,8 +444,8 @@ typedef struct LLVMProfileData_64 { * @return return AOT module loaded, NULL if failed */ AOTModule * -aot_load_from_aot_file(const uint8 *buf, uint32 size, char *error_buf, - uint32 error_buf_size); +aot_load_from_aot_file(const uint8 *buf, uint32 size, const LoadArgs *args, + char *error_buf, uint32 error_buf_size); /** * Load a AOT module from a specified AOT section list. diff --git a/core/iwasm/common/wasm_c_api.c b/core/iwasm/common/wasm_c_api.c index 29da8e22fa..456ce505e2 100644 --- a/core/iwasm/common/wasm_c_api.c +++ b/core/iwasm/common/wasm_c_api.c @@ -2234,7 +2234,8 @@ try_reuse_loaded_module(wasm_store_t *store, char *binary_hash) #endif /* WASM_ENABLE_WASM_CACHE != 0 */ wasm_module_t * -wasm_module_new(wasm_store_t *store, const wasm_byte_vec_t *binary) +wasm_module_new_ex(wasm_store_t *store, const wasm_byte_vec_t *binary, + const LoadArgs *args) { char error_buf[128] = { 0 }; wasm_module_ex_t *module_ex = NULL; @@ -2290,8 +2291,8 @@ wasm_module_new(wasm_store_t *store, const wasm_byte_vec_t *binary) if (!module_ex->binary->data) goto free_binary; - module_ex->module_comm_rt = wasm_runtime_load( - (uint8 *)module_ex->binary->data, (uint32)module_ex->binary->size, + module_ex->module_comm_rt = wasm_runtime_load_ex( + (uint8 *)module_ex->binary->data, (uint32)module_ex->binary->size, args, error_buf, (uint32)sizeof(error_buf)); if (!(module_ex->module_comm_rt)) { LOG_ERROR("%s", error_buf); @@ -2337,6 +2338,14 @@ wasm_module_new(wasm_store_t *store, const wasm_byte_vec_t *binary) return NULL; } +wasm_module_t * +wasm_module_new(wasm_store_t *store, const wasm_byte_vec_t *binary) +{ + LoadArgs args = { 0 }; + args.name = ""; + return wasm_module_new_ex(store, binary, &args); +} + bool wasm_module_validate(wasm_store_t *store, const wasm_byte_vec_t *binary) { diff --git a/core/iwasm/common/wasm_runtime_common.c b/core/iwasm/common/wasm_runtime_common.c index c7906edbeb..d93bb682e7 100644 --- a/core/iwasm/common/wasm_runtime_common.c +++ b/core/iwasm/common/wasm_runtime_common.c @@ -65,7 +65,7 @@ #if WASM_ENABLE_MULTI_MODULE != 0 /** * A safety insurance to prevent - * circular depencies which leads stack overflow + * circular dependencies which leads stack overflow * try to break early */ typedef struct LoadingModule { @@ -1333,11 +1333,15 @@ register_module_with_null_name(WASMModuleCommon *module_common, char *error_buf, } WASMModuleCommon * -wasm_runtime_load(uint8 *buf, uint32 size, char *error_buf, - uint32 error_buf_size) +wasm_runtime_load_ex(uint8 *buf, uint32 size, const LoadArgs *args, + char *error_buf, uint32 error_buf_size) { WASMModuleCommon *module_common = NULL; + if (!args) { + return NULL; + } + if (get_package_type(buf, size) == Wasm_Module_Bytecode) { #if WASM_ENABLE_INTERP != 0 module_common = @@ -1345,13 +1349,13 @@ wasm_runtime_load(uint8 *buf, uint32 size, char *error_buf, #if WASM_ENABLE_MULTI_MODULE != 0 true, #endif - error_buf, error_buf_size); + args, error_buf, error_buf_size); #endif } else if (get_package_type(buf, size) == Wasm_Module_AoT) { #if WASM_ENABLE_AOT != 0 module_common = (WASMModuleCommon *)aot_load_from_aot_file( - buf, size, error_buf, error_buf_size); + buf, size, args, error_buf, error_buf_size); #endif } else { @@ -1367,10 +1371,21 @@ wasm_runtime_load(uint8 *buf, uint32 size, char *error_buf, LOG_DEBUG("WASM module load failed"); return NULL; } + + /*TODO: use file name as name and register with name? */ return register_module_with_null_name(module_common, error_buf, error_buf_size); } +WASMModuleCommon * +wasm_runtime_load(uint8 *buf, uint32 size, char *error_buf, + uint32 error_buf_size) +{ + LoadArgs args = { 0 }; + args.name = ""; + return wasm_runtime_load_ex(buf, size, &args, error_buf, error_buf_size); +} + WASMModuleCommon * wasm_runtime_load_from_sections(WASMSection *section_list, bool is_aot, char *error_buf, uint32 error_buf_size) @@ -6501,6 +6516,7 @@ wasm_runtime_load_depended_module(const WASMModuleCommon *parent_module, bool ret = false; uint8 *buffer = NULL; uint32 buffer_size = 0; + LoadArgs args = { 0 }; /* check the registered module list of the parent */ sub_module = wasm_runtime_search_sub_module(parent_module, sub_module_name); @@ -6547,16 +6563,18 @@ wasm_runtime_load_depended_module(const WASMModuleCommon *parent_module, LOG_DEBUG("moudle %s type error", sub_module_name); goto destroy_file_buffer; } + + args.name = (char *)sub_module_name; if (get_package_type(buffer, buffer_size) == Wasm_Module_Bytecode) { #if WASM_ENABLE_INTERP != 0 - sub_module = (WASMModuleCommon *)wasm_load(buffer, buffer_size, false, - error_buf, error_buf_size); + sub_module = (WASMModuleCommon *)wasm_load( + buffer, buffer_size, false, &args, error_buf, error_buf_size); #endif } else if (get_package_type(buffer, buffer_size) == Wasm_Module_AoT) { #if WASM_ENABLE_AOT != 0 sub_module = (WASMModuleCommon *)aot_load_from_aot_file( - buffer, buffer_size, error_buf, error_buf_size); + buffer, buffer_size, &args, error_buf, error_buf_size); #endif } if (!sub_module) { diff --git a/core/iwasm/compilation/aot_llvm.c b/core/iwasm/compilation/aot_llvm.c index d4d1cff025..3af56e8b6b 100644 --- a/core/iwasm/compilation/aot_llvm.c +++ b/core/iwasm/compilation/aot_llvm.c @@ -85,7 +85,7 @@ aot_add_llvm_func1(const AOTCompContext *comp_ctx, LLVMModuleRef module, uint32 func_index, uint32 param_count, LLVMTypeRef func_type, const char *prefix) { - char func_name[48]; + char func_name[48] = { 0 }; LLVMValueRef func; LLVMValueRef local_value; uint32 i, j; diff --git a/core/iwasm/include/wasm_c_api.h b/core/iwasm/include/wasm_c_api.h index 606b9ff824..0d62c27518 100644 --- a/core/iwasm/include/wasm_c_api.h +++ b/core/iwasm/include/wasm_c_api.h @@ -517,10 +517,21 @@ struct WASMModuleCommon; typedef struct WASMModuleCommon *wasm_module_t; #endif +#ifndef LOAD_ARGS_OPTION_DEFINED +#define LOAD_ARGS_OPTION_DEFINED +typedef struct LoadArgs { + char *name; + /* TODO: more fields? */ +} LoadArgs; +#endif /* LOAD_ARGS_OPTION_DEFINED */ WASM_API_EXTERN own wasm_module_t* wasm_module_new( wasm_store_t*, const wasm_byte_vec_t* binary); +// please refer to wasm_runtime_load_ex(...) in core/iwasm/include/wasm_export.h +WASM_API_EXTERN own wasm_module_t* wasm_module_new_ex( + wasm_store_t*, const wasm_byte_vec_t* binary, const LoadArgs *args); + WASM_API_EXTERN void wasm_module_delete(own wasm_module_t*); WASM_API_EXTERN bool wasm_module_validate(wasm_store_t*, const wasm_byte_vec_t* binary); diff --git a/core/iwasm/include/wasm_export.h b/core/iwasm/include/wasm_export.h index b40a3440a2..e40e948852 100644 --- a/core/iwasm/include/wasm_export.h +++ b/core/iwasm/include/wasm_export.h @@ -183,6 +183,14 @@ typedef struct RuntimeInitArgs { bool enable_linux_perf; } RuntimeInitArgs; +#ifndef LOAD_ARGS_OPTION_DEFINED +#define LOAD_ARGS_OPTION_DEFINED +typedef struct LoadArgs { + char *name; + /* TODO: more fields? */ +} LoadArgs; +#endif /* LOAD_ARGS_OPTION_DEFINED */ + #ifndef INSTANTIATION_ARGS_OPTION_DEFINED #define INSTANTIATION_ARGS_OPTION_DEFINED /* WASM module instantiation arguments */ @@ -419,6 +427,13 @@ WASM_RUNTIME_API_EXTERN wasm_module_t wasm_runtime_load(uint8_t *buf, uint32_t size, char *error_buf, uint32_t error_buf_size); +/** + * Load a WASM module with specified load argument. + */ +WASM_RUNTIME_API_EXTERN wasm_module_t +wasm_runtime_load_ex(uint8_t *buf, uint32_t size, const LoadArgs *args, + char *error_buf, uint32_t error_buf_size); + /** * Load a WASM module from a specified WASM or AOT section list. * diff --git a/core/iwasm/interpreter/wasm_loader.c b/core/iwasm/interpreter/wasm_loader.c index d8ceb714ab..a07ce5866c 100644 --- a/core/iwasm/interpreter/wasm_loader.c +++ b/core/iwasm/interpreter/wasm_loader.c @@ -6043,7 +6043,7 @@ load_from_sections(WASMModule *module, WASMSection *sections, } static WASMModule * -create_module(char *error_buf, uint32 error_buf_size) +create_module(char *name, char *error_buf, uint32 error_buf_size) { WASMModule *module = loader_malloc(sizeof(WASMModule), error_buf, error_buf_size); @@ -6058,7 +6058,7 @@ create_module(char *error_buf, uint32 error_buf_size) /* Set start_function to -1, means no start function */ module->start_function = (uint32)-1; - module->name = ""; + module->name = name; #if WASM_ENABLE_FAST_INTERP == 0 module->br_table_cache_list = &module->br_table_cache_list_head; @@ -6138,7 +6138,7 @@ WASMModule * wasm_loader_load_from_sections(WASMSection *section_list, char *error_buf, uint32 error_buf_size) { - WASMModule *module = create_module(error_buf, error_buf_size); + WASMModule *module = create_module("", error_buf, error_buf_size); if (!module) return NULL; @@ -6479,9 +6479,9 @@ wasm_loader_load(uint8 *buf, uint32 size, #if WASM_ENABLE_MULTI_MODULE != 0 bool main_module, #endif - char *error_buf, uint32 error_buf_size) + const LoadArgs *args, char *error_buf, uint32 error_buf_size) { - WASMModule *module = create_module(error_buf, error_buf_size); + WASMModule *module = create_module(args->name, error_buf, error_buf_size); if (!module) { return NULL; } diff --git a/core/iwasm/interpreter/wasm_loader.h b/core/iwasm/interpreter/wasm_loader.h index 8b0dc77d61..676770ee22 100644 --- a/core/iwasm/interpreter/wasm_loader.h +++ b/core/iwasm/interpreter/wasm_loader.h @@ -28,7 +28,7 @@ wasm_loader_load(uint8 *buf, uint32 size, #if WASM_ENABLE_MULTI_MODULE != 0 bool main_module, #endif - char *error_buf, uint32 error_buf_size); + const LoadArgs *args, char *error_buf, uint32 error_buf_size); /** * Load a WASM module from a specified WASM section list. diff --git a/core/iwasm/interpreter/wasm_mini_loader.c b/core/iwasm/interpreter/wasm_mini_loader.c index f0859e96ec..dc96a194d6 100644 --- a/core/iwasm/interpreter/wasm_mini_loader.c +++ b/core/iwasm/interpreter/wasm_mini_loader.c @@ -2994,7 +2994,7 @@ load_from_sections(WASMModule *module, WASMSection *sections, } static WASMModule * -create_module(char *error_buf, uint32 error_buf_size) +create_module(char *name, char *error_buf, uint32 error_buf_size) { WASMModule *module = loader_malloc(sizeof(WASMModule), error_buf, error_buf_size); @@ -3009,7 +3009,7 @@ create_module(char *error_buf, uint32 error_buf_size) /* Set start_function to -1, means no start function */ module->start_function = (uint32)-1; - module->name = ""; + module->name = name; #if WASM_ENABLE_FAST_INTERP == 0 module->br_table_cache_list = &module->br_table_cache_list_head; @@ -3035,7 +3035,7 @@ WASMModule * wasm_loader_load_from_sections(WASMSection *section_list, char *error_buf, uint32 error_buf_size) { - WASMModule *module = create_module(error_buf, error_buf_size); + WASMModule *module = create_module("", error_buf, error_buf_size); if (!module) return NULL; @@ -3206,10 +3206,10 @@ load(const uint8 *buf, uint32 size, WASMModule *module, char *error_buf, } WASMModule * -wasm_loader_load(uint8 *buf, uint32 size, char *error_buf, +wasm_loader_load(uint8 *buf, uint32 size, const LoadArgs *args, char *error_buf, uint32 error_buf_size) { - WASMModule *module = create_module(error_buf, error_buf_size); + WASMModule *module = create_module(args->name, error_buf, error_buf_size); if (!module) { return NULL; } diff --git a/core/iwasm/interpreter/wasm_runtime.c b/core/iwasm/interpreter/wasm_runtime.c index a216b4e29d..688f1c2c14 100644 --- a/core/iwasm/interpreter/wasm_runtime.c +++ b/core/iwasm/interpreter/wasm_runtime.c @@ -60,13 +60,13 @@ wasm_load(uint8 *buf, uint32 size, #if WASM_ENABLE_MULTI_MODULE != 0 bool main_module, #endif - char *error_buf, uint32 error_buf_size) + const LoadArgs *name, char *error_buf, uint32 error_buf_size) { return wasm_loader_load(buf, size, #if WASM_ENABLE_MULTI_MODULE != 0 main_module, #endif - error_buf, error_buf_size); + name, error_buf, error_buf_size); } WASMModule * diff --git a/core/iwasm/interpreter/wasm_runtime.h b/core/iwasm/interpreter/wasm_runtime.h index 3b01f05cd5..13b738f9e7 100644 --- a/core/iwasm/interpreter/wasm_runtime.h +++ b/core/iwasm/interpreter/wasm_runtime.h @@ -508,7 +508,7 @@ wasm_load(uint8 *buf, uint32 size, #if WASM_ENABLE_MULTI_MODULE != 0 bool main_module, #endif - char *error_buf, uint32 error_buf_size); + const LoadArgs *args, char *error_buf, uint32 error_buf_size); WASMModule * wasm_load_from_sections(WASMSection *section_list, char *error_buf, diff --git a/samples/linux-perf/CMakeLists.txt b/samples/linux-perf/CMakeLists.txt new file mode 100644 index 0000000000..e9882c835f --- /dev/null +++ b/samples/linux-perf/CMakeLists.txt @@ -0,0 +1,63 @@ +# Copyright (C) 2019 Intel Corporation. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +cmake_minimum_required (VERSION 3.14) + +project(linux_perf_sample) + +if(NOT CMAKE_HOST_LINUX) + message(FATAL_ERROR "This sample only works on linux") +endif() + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +set(CMAKE_CXX_STANDARD 17) + +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) +find_package(WASISDK REQUIRED) + +################ runtime settings ################ +string (TOLOWER ${CMAKE_HOST_SYSTEM_NAME} WAMR_BUILD_PLATFORM) +include(CheckPIESupported) + +# AOT and JIT byd default +set(WAMR_BUILD_AOT 1) +set(WAMR_BUILD_INTERP 0) +set(WAMR_BUILD_JIT 1) +# wasm32-wasi +set(WAMR_BUILD_LIBC_BUILTIN 0) +set(WAMR_BUILD_LIBC_WASI 1) +# mvp +set(WAMR_BUILD_BULK_MEMORY 1) +set(WAMR_BUILD_REF_TYPES 1) +set(WAMR_BUILD_SIMD 1) +set(WAMR_BUILD_TAIL_CALL 1) +# trap information +set(WAMR_BUILD_DUMP_CALL_STACK 1) +# linux perf +set(WAMR_BUILD_LINUX_PERF 1) +# +#set(WAMR_BUILD_THREAD_MGR 0) + +# vmlib +set(WAMR_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..) +include(${WAMR_ROOT_DIR}/build-scripts/runtime_lib.cmake) +add_library(vmlib SHARED ${WAMR_RUNTIME_LIB_SOURCE}) +target_include_directories(vmlib INTERFACE ${WAMR_ROOT_DIR}/core/iwasm/include) +target_link_libraries (vmlib ${LLVM_AVAILABLE_LIBS} -lm -ldl) + +################ host ################ +add_executable(${PROJECT_NAME} host/demo.c) +target_link_libraries(${PROJECT_NAME} vmlib) + +################ aot + wasm ################ +include(ExternalProject) +ExternalProject_Add(wasm + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/wasm" + CONFIGURE_COMMAND ${CMAKE_COMMAND} -S ${CMAKE_CURRENT_SOURCE_DIR}/wasm -B build + -DCMAKE_TOOLCHAIN_FILE=${WASISDK_TOOLCHAIN} + BUILD_COMMAND ${CMAKE_COMMAND} --build build + INSTALL_COMMAND ${CMAKE_COMMAND} --install build --prefix ${CMAKE_CURRENT_BINARY_DIR} +) \ No newline at end of file diff --git a/samples/linux-perf/README.md b/samples/linux-perf/README.md new file mode 100644 index 0000000000..5a8dc578fb --- /dev/null +++ b/samples/linux-perf/README.md @@ -0,0 +1,90 @@ +# linux perf sample introduction + +This is a sample to show how to use the Linux perf tool to profile the execution of a WebAssembly application. And how to use the [Flamegraph](https://www.brendangregg.com/flamegraphs.html) tool to visualize the profiling result. + +## Build and run the sample + +There are two Wasm modules and their instance will be created and run in the sample. [The first module](./wasm/fib.c) is a simple Wasm module that calculates the Fibonacci number. [The second module](./wasm/ackermann.c) is a simple Wasm module that execute the Ackermann function. The target is enable to profile the execution of both two modules separately. + +```bash +$ cmake -S . -B build +$ cmake --build build +``` + +### Profile the execution + +```bash +$ cd build +$ perf record -k mono -g --output=perf.data -- ./linux_perf_sample +``` + +Enable to use `perf report --stdio` to do a quick analysis of the profiling result. + +### Visualize the profiling result + +Need to download Flamegraph tool from [Flamegraph](https://github.com/brendangregg/FlameGraph/releases/tag/v1.0) firstly. + +```bash +$ perf script > out.perf +$ ./FlameGraph/stackcollapse-perf.pl out.perf > out.folded +$ ./FlameGraph/flamegraph.pl out.folded > perf.svg +``` + +In this result, you'll see two modules's profiling result and all wasm functions are named as "aot_func#N" which is a little hard to distinguish. + +![perf.png](./pics/perf.png) + +### Separate profiling result + +[process_folded_data.py](../../test-tools/flame-graph-helper/process_folded_data.py) is a script can a) translate "aot_func#N" into its original function name in name sections, b) separate the profiling result of different modules. + +In this sample, we want to separate `fib` and `ackermann` profiling data from _out.folded_. In [demo](host/demo.c), we decide to name the module of `fib1.wasm` as `fib2` and the module of `ackermann1.wasm` as `ackermann2`. + +```bash +$ python process_folded_data.py --wabt_home /opt/wabt --wasm_names fib2=./fib1.wasm,ackermann2=./ackermann1.wasm out.folded +-> write into out.fib2.translated +-> write into out.ackermann2.translated +-> write into out.translated +``` + +More scenarios: + +if only using one wasm during profiling, the script can be used like this: + +```bash +$ python process_folded_data.py --wabt_home /opt/wabt --wasm --folded +``` + +if only using one wasm during profiling and specify the module name via APIs, the script can be used like this: + +```bash +$ python process_folded_data.py --wabt_home /opt/wabt --wasm_names = --folded +``` + +if only using one wasm during profiling and specify the module name, which is same with the basename of wasm file, via APIs, the script can be used like this: + +```bash +$ python process_folded_data.py --wabt_home /opt/wabt --wasm --folded +``` + +if using multiple wasm during profiling and specify module names, which are same with basename of wasm files, via APIs, the script can be used like this: + +```bash +$ python process_folded_data.py --wabt_home /opt/wabt --wasm --wasm --wasm --folded +``` + +if using multiple wasm during profiling and specify module names via APIs, the script can be used like this: + +```bash +$ python process_folded_data.py --wabt_home /opt/wabt --wasm_names =,=,= --folded +``` + +Now we have two flame-graphs for two wasm modules: + +![fib.svg](./pics/perf.fib.svg) + +![ackermann.svg](./pics/perf.ackermann.svg) + +## Reference + +- [perf_tune](../../doc/perf_tune.md) diff --git a/samples/linux-perf/cmake/FindWAMRC.cmake b/samples/linux-perf/cmake/FindWAMRC.cmake new file mode 100644 index 0000000000..586263eddd --- /dev/null +++ b/samples/linux-perf/cmake/FindWAMRC.cmake @@ -0,0 +1,14 @@ +# Copyright (C) 2019 Intel Corporation. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +include(FindPackageHandleStandardArgs) + +find_file(WAMRC_BIN + NAMES wamrc + DOC "search wamrc" + HINTS ${CMAKE_CURRENT_SOURCE_DIR}/../../../wamr-compiler/build + REQUIRED +) + +find_package_handle_standard_args(WAMRC REQUIRED_VARS WAMRC_BIN) +mark_as_advanced(WAMRC_BIN) \ No newline at end of file diff --git a/samples/linux-perf/cmake/FindWASISDK.cmake b/samples/linux-perf/cmake/FindWASISDK.cmake new file mode 100644 index 0000000000..5cdfea41e4 --- /dev/null +++ b/samples/linux-perf/cmake/FindWASISDK.cmake @@ -0,0 +1,23 @@ +# Copyright (C) 2019 Intel Corporation. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +include(FindPackageHandleStandardArgs) + +file(GLOB WASISDK_SEARCH_PATH "/opt/wasi-sdk-*") +find_path(WASISDK_HOME + NAMES share/wasi-sysroot + PATHS ${WASISDK_SEARCH_PATH} + NO_DEFAULT_PATH + REQUIRED +) + +string(REGEX MATCH [0-9]+\.[0-9]+\.*[0-9]* WASISDK_VERSION ${WASISDK_HOME}) + +find_package_handle_standard_args(WASISDK REQUIRED_VARS WASISDK_HOME VERSION_VAR WASISDK_VERSION) + +if(WASISDK_FOUND) + set(WASISDK_CC_COMMAND ${WASISDK_HOME}/bin/clang) + set(WASISDK_CXX_COMMAND ${WASISDK_HOME}/bin/clang++) + set(WASISDK_TOOLCHAIN ${WASISDK_HOME}/share/cmake/wasi-sdk.cmake) + set(WASISDK_SYSROOT ${WASISDK_HOME}/share/wasi-sysroot) +endif() diff --git a/samples/linux-perf/host/demo.c b/samples/linux-perf/host/demo.c new file mode 100644 index 0000000000..8ea446ed4c --- /dev/null +++ b/samples/linux-perf/host/demo.c @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2019 Intel Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include +#include +#include +#include +#include + +#include "wasm_c_api.h" + +#define own + +/* return a copy of the file stem of a file path */ +static own char * +stem(const char *file_path) +{ + char *base_name = basename(file_path); + char *s = strdup(base_name); + char *dot = strchr(s, '.'); + assert(dot); + *dot = '\0'; + return s; +} + +static void +guest_i32_to_wasm_i32_array(int *args, unsigned argc, wasm_val_t *data, + unsigned datac) +{ + for (unsigned i = 0; i < argc && i < datac; i++) { + memset(&data[i], 0, sizeof(wasm_val_t)); + data[i].kind = WASM_I32; + data[i].of.i32 = args[i]; + } +} + +int +load_run_wasm_file(wasm_engine_t *engine, const char *file_path, int *args, + unsigned argc) +{ + wasm_store_t *store = wasm_store_new(engine); + // Load binary. + printf("Loading binary...\n"); + FILE *file = fopen(file_path, "rb"); + assert(file); + + int ret = fseek(file, 0L, SEEK_END); + assert(ret == 0); + + long file_size = ftell(file); + assert(file_size != -1); + + ret = fseek(file, 0L, SEEK_SET); + assert(ret == 0); + + wasm_byte_vec_t binary = { 0 }; + wasm_byte_vec_new_uninitialized(&binary, file_size); + + size_t nread = fread(binary.data, file_size, 1, file); + fclose(file); + + // Compile. + printf("Compiling module...\n"); + + // Use its file name as the module name + char *file_name = stem(file_path); + assert(file_name); + + LoadArgs load_args = { 0 }; + load_args.name = file_name; + own wasm_module_t *module = wasm_module_new_ex(store, &binary, &load_args); + wasm_byte_vec_delete(&binary); + assert(module); + + // Use export type to find the function index to call later + wasm_exporttype_vec_t export_types = { 0 }; + wasm_module_exports(module, &export_types); + int func_to_call = -1; + for (unsigned i = 0; i < export_types.num_elems; i++) { + const wasm_name_t *name = wasm_exporttype_name(export_types.data[i]); + if (strncmp(name->data, "run", 3) == 0) { + func_to_call = i; + break; + } + } + assert(func_to_call != -1); + + // Instantiate. + printf("Instantiating module...\n"); + wasm_extern_vec_t imports = WASM_EMPTY_VEC; + own wasm_instance_t *instance = wasm_instance_new_with_args( + store, module, &imports, NULL, 16 * 1024 * 1024, 1 * 1024 * 1024); + assert(instance); + + // Extract export. + printf("Extracting export...\n"); + own wasm_extern_vec_t exports; + wasm_instance_exports(instance, &exports); + assert(exports.size); + + assert(wasm_extern_kind(exports.data[func_to_call]) == WASM_EXTERN_FUNC); + const wasm_func_t *run_func = + wasm_extern_as_func(exports.data[func_to_call]); + assert(run_func); + + wasm_module_delete(module); + wasm_instance_delete(instance); + + // Call. + printf("Calling export...\n"); + wasm_val_t as[4] = { 0 }; + guest_i32_to_wasm_i32_array(args, argc, as, 4); + + wasm_val_vec_t params = WASM_ARRAY_VEC(as); + wasm_val_t rs[1] = { WASM_I32_VAL(0) }; + wasm_val_vec_t results = WASM_ARRAY_VEC(rs); + wasm_trap_t *trap = wasm_func_call(run_func, ¶ms, &results); + assert(!trap); + + wasm_extern_vec_delete(&exports); + free(file_name); + wasm_store_delete(store); + + { + nread = nread; + ret = ret; + trap = trap; + } + return 0; +} + +void * +load_run_fib_wasm(void *arg) +{ + wasm_engine_t *engine = (wasm_engine_t *)arg; + int args[] = { 40 }; + load_run_wasm_file(engine, "./fib1.wasm", args, 1); + return NULL; +} + +void * +load_run_fib_aot(void *arg) +{ + wasm_engine_t *engine = (wasm_engine_t *)arg; + int args[] = { 40 }; + load_run_wasm_file(engine, "./fib2.aot", args, 1); + return NULL; +} + +void * +load_run_ackermann_wasm(void *arg) +{ + wasm_engine_t *engine = (wasm_engine_t *)arg; + int args[] = { 3, 12 }; + load_run_wasm_file(engine, "./ackermann1.wasm", args, 2); + return NULL; +} + +void * +load_run_ackermann_aot(void *arg) +{ + wasm_engine_t *engine = (wasm_engine_t *)arg; + int args[] = { 3, 12 }; + load_run_wasm_file(engine, "./ackermann2.aot", args, 2); + return NULL; +} + +int +main(int argc, const char *argv[]) +{ + // Initialize. + printf("Initializing...\n"); + wasm_config_t *config = wasm_config_new(); + wasm_config_set_linux_perf_opt(config, true); + wasm_engine_t *engine = wasm_engine_new_with_config(config); + + pthread_t tid[4] = { 0 }; + /* FIXME: uncomment when it is able to run two modules with llvm-jit */ + // pthread_create(&tid[0], NULL, load_run_fib_wasm, (void *)engine); + // pthread_create(&tid[2], NULL, load_run_ackermann_wasm, (void *)engine); + + pthread_create(&tid[1], NULL, load_run_fib_aot, (void *)engine); + pthread_create(&tid[3], NULL, load_run_ackermann_aot, (void *)engine); + + for (unsigned i = 0; i < sizeof(tid) / sizeof(tid[0]); i++) + pthread_join(tid[i], NULL); + + // Shut down. + printf("Shutting down...\n"); + wasm_engine_delete(engine); + + // All done. + printf("Done.\n"); + return 0; +} diff --git a/samples/linux-perf/pics/perf.ackermann.svg b/samples/linux-perf/pics/perf.ackermann.svg new file mode 100644 index 0000000000..c2e1d87472 --- /dev/null +++ b/samples/linux-perf/pics/perf.ackermann.svg @@ -0,0 +1,1349 @@ + + + + + + + + + + + + + + +Flame Graph + +Reset Zoom +Search +ic + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +load_run_wasm_file (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +invoke_ii_i (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +load_run_ackermann_aot (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] run (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +wasm_runtime_call_wasm (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +all (11,485,868,643 samples, 100%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +aot_call_function (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +linux_perf_samp (11,485,868,643 samples, 100.00%) +linux_perf_samp + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +wasm_func_call (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +invoke_native_with_hw_bound_check (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +start_thread (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (11,484,299,081 samples, 99.99%) +[Wasm] [ackermann2] ackermann + + +[Wasm] [ackermann2] ackermann (1,569,562 samples, 0.01%) + + + + diff --git a/samples/linux-perf/pics/perf.fib.svg b/samples/linux-perf/pics/perf.fib.svg new file mode 100644 index 0000000000..a1db059a7a --- /dev/null +++ b/samples/linux-perf/pics/perf.fib.svg @@ -0,0 +1,605 @@ + + + + + + + + + + + + + + +Flame Graph + +Reset Zoom +Search +ic + + + +[Wasm] [fib2] fibonacci (1,321,095,222 samples, 93.80%) +[Wasm] [fib2] fibonacci + + +[Wasm] [fib2] fibonacci (382,407,564 samples, 27.15%) +[Wasm] [fib2] fibonacci + + +[Wasm] [fib2] fibonacci (15,340,273 samples, 1.09%) + + + +[Wasm] [fib2] fibonacci (1,359,552,763 samples, 96.53%) +[Wasm] [fib2] fibonacci + + +[Wasm] [fib2] fibonacci (1,408,481,525 samples, 100.00%) +[Wasm] [fib2] fibonacci + + +[Wasm] [fib2] fibonacci (27,274,310 samples, 1.94%) +[.. + + +[Wasm] [fib2] fibonacci (62,450,767 samples, 4.43%) +[Wasm.. + + +[Wasm] [fib2] fibonacci (1,408,481,525 samples, 100.00%) +[Wasm] [fib2] fibonacci + + +[Wasm] [fib2] fibonacci (1,388,674,508 samples, 98.59%) +[Wasm] [fib2] fibonacci + + +[Wasm] [fib2] fibonacci (1,170,751,868 samples, 83.12%) +[Wasm] [fib2] fibonacci + + +[Wasm] [fib2] fibonacci (1,408,481,525 samples, 100.00%) +[Wasm] [fib2] fibonacci + + +[Wasm] [fib2] fibonacci (120,820,158 samples, 8.58%) +[Wasm] [fib2.. + + +invoke_i_i (1,408,481,525 samples, 100.00%) +invoke_i_i + + +[Wasm] [fib2] fibonacci (1,375,872,224 samples, 97.68%) +[Wasm] [fib2] fibonacci + + +load_run_wasm_file (1,408,481,525 samples, 100.00%) +load_run_wasm_file + + +load_run_fib_aot (1,408,481,525 samples, 100.00%) +load_run_fib_aot + + +[Wasm] [fib2] run (1,408,481,525 samples, 100.00%) +[Wasm] [fib2] run + + +[Wasm] [fib2] fibonacci (42,420,273 samples, 3.01%) +[Wa.. + + +[Wasm] [fib2] fibonacci (1,266,323,684 samples, 89.91%) +[Wasm] [fib2] fibonacci + + +linux_perf_samp (1,408,481,525 samples, 100.00%) +linux_perf_samp + + +[Wasm] [fib2] fibonacci (280,259,464 samples, 19.90%) +[Wasm] [fib2] fibonacci + + +start_thread (1,408,481,525 samples, 100.00%) +start_thread + + +[Wasm] [fib2] fibonacci (2,334,521 samples, 0.17%) + + + +[Wasm] [fib2] fibonacci (666,394,609 samples, 47.31%) +[Wasm] [fib2] fibonacci + + +[Wasm] [fib2] fibonacci (943,121,736 samples, 66.96%) +[Wasm] [fib2] fibonacci + + +[Wasm] [fib2] fibonacci (1,169,581 samples, 0.08%) + + + +[Wasm] [fib2] fibonacci (1,169,581 samples, 0.08%) + + + +[Wasm] [fib2] fibonacci (194,755,877 samples, 13.83%) +[Wasm] [fib2] fibonacci + + +[Wasm] [fib2] fibonacci (1,406,148,966 samples, 99.83%) +[Wasm] [fib2] fibonacci + + +all (1,408,481,525 samples, 100%) + + + +wasm_func_call (1,408,481,525 samples, 100.00%) +wasm_func_call + + +[Wasm] [fib2] fibonacci (5,943,602 samples, 0.42%) + + + +[Wasm] [fib2] fibonacci (1,408,481,525 samples, 100.00%) +[Wasm] [fib2] fibonacci + + +wasm_runtime_call_wasm (1,408,481,525 samples, 100.00%) +wasm_runtime_call_wasm + + +[Wasm] [fib2] fibonacci (1,401,486,191 samples, 99.50%) +[Wasm] [fib2] fibonacci + + +aot_call_function (1,408,481,525 samples, 100.00%) +aot_call_function + + +[Wasm] [fib2] fibonacci (531,941,563 samples, 37.77%) +[Wasm] [fib2] fibonacci + + +[Wasm] [fib2] fibonacci (1,406,148,966 samples, 99.83%) +[Wasm] [fib2] fibonacci + + +[Wasm] [fib2] fibonacci (1,061,055,435 samples, 75.33%) +[Wasm] [fib2] fibonacci + + +[Wasm] [fib2] fibonacci (1,408,481,525 samples, 100.00%) +[Wasm] [fib2] fibonacci + + +[Wasm] [fib2] fibonacci (1,403,816,880 samples, 99.67%) +[Wasm] [fib2] fibonacci + + +[Wasm] [fib2] fibonacci (800,646,766 samples, 56.84%) +[Wasm] [fib2] fibonacci + + +invoke_native_with_hw_bound_check (1,408,481,525 samples, 100.00%) +invoke_native_with_hw_bound_check + + + diff --git a/samples/linux-perf/pics/perf.png b/samples/linux-perf/pics/perf.png new file mode 100755 index 0000000000000000000000000000000000000000..fc4c0930fc289d0df96fea22d05dee3e0737e1b8 GIT binary patch literal 92293 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

v>`UJ>F@`Z-7dProU^u(y>< zuKNy^&#DP;fNBqqps`G02szYoKH^pcEuUdab#KIzwYO7@;y{L3o&O8`EVmY~wQjJ~ z%*sj6&uNpEc$8_h^^N#Pj?IFiBxg3@7Q#^fnj!Jm47wk$`W{WZ1x`;~xr!p`c~2Ik zCY6pBMLB^qU{%)a>qimA_n(?i)iECN_zZ7T#W;xkvQMgX@-zQr&E+eV4z{IyKc0^a zzDLlm*{V0%ph=>d*0T(^qiLmLw>W9m3`UirwO+pYdVi${D*5I^tzWAM{|2(Yd47nJi58Rf ztHLJmgu_ns<_5!+t1VN&AM&(WEhvtf`S6QO6vh5VDpM1A3_223{W(>+7@;c@!MPDG zmrAYJ>@1Cu*43y$aTs>!A4cyH4=%X?U#j$t6~3NjADcDVu+0dVYdGspqK%*L|Cnz%Kg5Jdox(lktY4RfMH%)0`F_V1v`jt(5gwlTJH;>UeIB07E(G!Fj%}U)xzCxF?op) zc~wxUcCn_xhf^7;QK-*EQWD7~=JRt6Kd|I>ZUcB96F1oo*zPb5hA zdwQ|USCH{YcqOr@OQytj;fr$Yf#TsB9XH)aXO6k+q8@{b<|MG#2#ZVzn5|KBw*C%x zuw~|4guiV^uV~#M{OA2Y1I*`|w`<a+CR93M}7$!++{sqKT%&!)=i_TsZ-r(6`##V zL(L0|z|C_6H0kE+?ZEtTcd@rPP{kX`zNG+$5q9ZUWS&Anq6w+oob^0xLz{z)++g+d zD$1hQy$O*j5dco;b)s4>B;6i@mE2LM65Iyx=6c;}7(l45%|DD5CTWtPaC^$zELjnR4`w|!`A;>@qZyEv~Znp4}+TE^KxRr`{M%i)zaMxTqzHP z!>q=3W%KM~i2?1$ zfGUyXwE^ot9jH7(vE#g*jz|-!zUkPP!#;GP(O)N{4e!e}-Sy9(`!TDGEr9OP+jSqj zK4j!90-X6yTcmS};j=-?SBeJ~Qw|-nrEP*h2@c@VERZKq z8+SYx7-HrDwIZ*Prd$jZLlVr~#GbY;7KlU?J zzA}fBovhlqZn_c05KP)ywvHG&%{jIDDy=KKOG|vYchnyHW@ra-PO>gCZK7I^XE!x0 z>AK;S&HHOhH8P_1%uM1P=aT-GDt=~2+?#@en+C?z;zr{=4@m^+85jEbKA1i4zqvXH zRC)X*4clU;=2EX9U+-ZMcb1Flm5dqxxxWc7!m_U|2STF^)VeMpWpPy6p@cQ_7S z>^*)Lm@(t4OltyuvgNTBBfJ|v*ZrRcR&i{d|Ds(<2-@} zAY33-L}C`f!pgKKhE)H%5s}}+QZvFBUk4feVyOaTObpy^eOdk)Pqem6YtKti&eyTx zSM({^B0B9I*zI^ggJew;(-H3ieN;FCKcNkgPn^lKPXjhQ~;yu@Ly@+(?eNNP%Gm6xwo7$}foLe*7QZg^7R_eLRP%2l_4#_Hwa zG9>DG_hSIV_ptO?27dyvg?tOLP?+(aff7SUUdg-l2s8wDY#?cF2`$_$e1DZ}(;sun(8LeDw*tJMYv+nF~7USTUZ3TVvIVv^AF??X+O< z3}ITNNA&%}%_M!XuE$nBdjHEo+OJQfrCrLTC(nY{E4ExW1nwT1>xKtohRFB*;a>`; z@SilNWM+(ArOmP@d+PY`Bt>cS7KNrU1@@A_V}+q#n2d=jNNI(T}8XR?3(yzjoKz7jQgEge=r&SN{M%U-qt#wIUmm| zeRw+u${*}lx5FkZ?}Y~uN{uWSw|K1tO>teyS~3TcnTF&LB$I$-YmR;&o3W~qaF9Q@ zywS|(EIV@(ojt1PGfh4;Q7eMW6XrrM$AWGSo?FKzS!t%SDX1(zS54L}DtVh^mFxOA z?~~ZNvG;ZbaSo04n0_mU3%<0Vwys~gG$j^T|A_*_9vbpzXo7~(0e#UzPI|~vju`oP zB3@0EE=yZpshy?YjRI`36d5#S(WUzQg`H4Zb%U!Nt%YDs{ zy4MTCgH`)R{8R7#Td}00-aFXPWCzHxr>PV{-o4J%QLn4SNMh%{>c3&NKF#- zQ_g<|MY9@#bU&S2|DwI=SumKtRL|7Pe#3%LKKPRyTM91hT{!)c!Ss`4b0Ex8ds z9(Zbbi9r_7;k%6+c#2a3{S*0pO8!m7`9NjSi_(7Sor59;;dT9$5Z@VGEX?pU+a9llBx_tg;5a*M*)!EyBs}B+`mf+6M*Tj_!g`Tw&5fokJqH{0~ zTd1{a;PilP%dy`V9oRr}XnYZkBS(CE%W2HnG&^%K)PlPUI0=sqcIQmh4Lhy3qaOJF z8OjlF{CVam3$tOq-ynrcw`*nOv8?*Ug5P`A_TDI)*TZgsbK%<|Kc;F49%mA29bOi| zqOsznm$L{y_EP$2#iNx5m;ip3y(AOU;AQFkXujR8`dXf3q zOEdS*f3k@SirqGaqvMD<_eXiS1UuGO)Ql){bkydotF>7*j}lNHZdL_9*C_^@5K=M0 zbDTq&aew*{Jlb8k`s4a$ak9+@SJBN9IKr76*xUBAllR9YfyVtedUna$JhK8TS~Erk z78xmbMasBXB3gDSDV$|$ICk{_-{hcbW+{WZi(dBo>}&u^s50((*(T!nfsK~spSv6u*9=pG)r{A4Zp#8zm=1r zb=xIl&yzgs6K+2DMup3?>nHnF%MT5(HCZ=r`8uRU`A?OIB;!RD})}T?=vZfG2{D4f%g1V~@5FhijY~olm4#)8W9qt+KyucUJF80O7D0SH~JEpS8d8K5YWe1<#z3 z9P9?}AkGjyH#n_P^(Za9pUYeWd;jeT+7)@|i2>89A`R78R=rg$6!Ca)@R1vmNYueN zJ+x;D#V3zvCcNbkBh{r zoebqh;tJxgCDlNOpOiG9Twu|3o$#pq1M;=@fpwjijR!Y;}`0$Bz5j#Yg4{F4!E4iV$v($O4n0xjW>VR#eFITZNv#AKeZxh=_2l#7%5mTOTkB zWfoJOAm&SJ^bl`zj*N&Cqma4;iZ~p^I7~eX&^o{+S`-6gMFmR4E+mPmyCOf0T9FSY z&D-@-b#V20i6ba@U@MS^JqQae!@~cqJ)E)O84<rBJq4`_KRKz1Y+`e2>x6S9%FZa8XA<=`<%4|4$hMP zTTKxYL~*YItaU%dCJVg8lIWjm3kD5!yzbtyc1>#hUfA$88ER910RF4|r?32O>>Rb0 zzFUdVGIqwYbV^uSr^c@O?RrQJd1&|>@am%E=q9Dv))>@dt-jbK$$CW(#- z4JcDu`Ot;_EZk}Ik4-f~LEEm%cD}c0`@Oo*jv|d{EdTcf7l%!SwVc0R@`Ne(?NE2l){NS%jJaYYarVm#&&jDe16w zE~ZZlS+@~L?nyb3nWRw)#zqcu=ai16CMA`nrU*2he?c42G#*E$vgT7*|Md}w4PGXP zHh~5LGYLKSD{*}*mi}^SSMoc4-A50aFZDMQY->Y!q8VqkyB)jyaYoi?v8jpao9C-= zl-P_XyQyO4&>Sy!TCPyv=3tvEnvI%t=*=641xS+4clbA`Bfy8cdK*^xBj-2wWi-0u z;V`Azotaki&gRPIPCRMFZ@kQLxm`;q3>`|H5&>hk)x?N_fj8IbYBVkd@mvsKbx)63kvL& zo(iL+k_uwI4gV@)^TwcO+IFq-bp=6Xd59!e_qZqONZLi#HLTG@tLsn^de%xTc>p>$ z_PMA7tlZ1H$M$g)Xp{0;W@QL3MX6uRwa3hWR*;pE^(d}K)39t$%NS0t*gjFLWw|r` zFSGN{(({S$RO?vTGCRLbUqZ-{vY)*A6&ug)`d$e7tqvIjbEkcQXKcKumM%^eAE5zw z#x`))Xghx8XN381->2p?B|_n$xv%13(%LvCfhPRy3Ksu)9&OzIHm9>sH2U0)R2)HO|swo|9--liyfbUp1PvzfQrZs3~tC z&QwF|#eu0+^Z}2je;GBh2L?txqo& zxmMYDECxBvXxU;yC7(MEG~b4GZPA;k5ox=X8DVci)v`T|x}@k&K!NV(%3Nge&HXCR z0<_!^pDW~l_aiulgt!ex`IMQ*_|4m%RjJ&K|IMzJ(V?lDL?tk5aA2@OGqnVwYvZE*R^nb%U#3Cv8(WW!CiKXb z$!>(6C-}t;5+@u}|78vY*q-vxPhE=~oE*h2%^#k!M{isUmm|(4=CkSIg1(1FXWb;V zI6b;S&&7k`t2chSKu|8E32_UfBa0GShq2oP)?2~aTe)X0z$0!X&>KatP7LvU*aI$u zQye-}2}GDGAGbIV%eAqq4nOckd~g17&|B8QHYdn@$DVe{6yP?zjqAZ69B}fI+KM$%au$HR^=gtij72kgce`pj*9`U4?TTfsG=3$fky$golEg{%5;Svd@rqYdB z|7n?F;@Kqe-_VTnM8~&}|GQt|WHJ4&QM^t|!sU?%uTl0hn&)RNhB&TF(=dD$*4J>J zb{7Y`_fZh(hQTf%t@?8cJa|XrH|o7Nyi`-q&+0{r4Ar!3H2gNjY(vRIMTkhV>gtz) zL{$~p@XTh1zt!dXp+@y2lrj0M*wi-;ZA2<1N{P%W$!4N+t2S#B20lC#wen^Dwy?hv zd)3+++eq4t-Sz*_57MKDLg`AYP8j6+7JFry&G=5)WNCeWF{mEYF1do0XF?Hcg>)(c z=r6ovvCeqSUIJmPCUmc*DzBFO<`&W-7H+C2?w>M3Q^Z~o3POKi0@v%}-jVD|`bZ## zOOdC?fkwpri^=p2MtZ&xCZ_+>m!J&wl7@40ipv?g!ED>|{watA>J=K;c;RYa-6yYYS^Gbrl4 zKkYK4D(t{Ko$o;pLX*Za_6czFAMcc6mCk$_SJ_V7DWIM2-^uOdv|W75bJGU`)^O4s z$_+gh0Xz$n!S!m&*{3x2vP#e}S$_)>N)ij;F~^!F-`zPX&Mpzv>0Ylu>Dar1?(Y}g z(dZ!Un>+?WZ!V(D`4EaRSBXrYnw2T2pT$A#vK!RV37BFzc%YrjdfOo;>NXyMS3QbV z5!6z^l@x6gUUgpD7fd0z++C;ms$x>*Wkw}{v&xs>=Dhz2G&UcucNZpYMfJpndaV9U zwQiq@yF0qH;YadGGVt+pXio_mb=+*JEMTL`#wn1W^RAJ<-a@njHx0YVOEOqe>Le$JzS|FpwA@Kj$b7r zu3JW`-LK3)O2A9Cp6wz187h5+*xmYj+h{{2Ylg>tIu@FCWxyFv{V;rsE*9S;3pJ?_ zRnv@=H}B@P|JbcN+IIts!OK@QU{ZK*)MkdaFP9c^I_Kfvl!s22fvJ!Ttu&|=^cgpe zsbNogyQ!UhQ=z<9dHX(5fMnyt<~02&VJ_}{pgq(47mY9l6OGBiCw{3$&z;bW|YHhW^fO<)n%~{2lFQM z^}mw!JoXtQ4zp6oXDjPTA#v93^dykb$16fuAX!Jj>EC!+M0Jb@&VZ)wC-*!#B8&m| zEDD4l?Ju2`BilKJNB_R5veok9Deo_L1}1BPyT@Hp~%>5J-Q_8kcN3{!MCMouEZ#x$ct*9O5KsH359a7C=#HPoS+!m5m^&+>AJ3>?1)_#6 z8V|?odscbSt}tMwvDqzccJ)T!;<_IDvoEpcQCj+=OSiVtLhjGCdxz!J;eYJMn$2yJ z4RR{hzXuktBw(PDDW?~pKn}hjLBy;H1NZhZPtACC+cTG@!nz)jzeH~bAmaOS=H9sJ z$3m-)N#wGmqeroG=~EF&b^Cnq^ovjqi9ln8ca`h}p$Fs=tHC8 z{o9vY$nh!ze=bzXq-%7{{`Nj7L3y?*)240pnO$EwklJDC(n8L)l?TBrx~3Z;1)kF2 z>Fxkm3TnXKfr+_`!9>swQqx=jrbze=`~Yw!)AZNqr3&(T6>jcqTM=lX#M0t{s(WXO zAuts$~n7|=yci$hw&#%ZA6xGK8xR%lv&4x+bO8R9F1-{sKhcMr=Rt*e|tmv`7O z=rC8AWRuzWnecW6jfupRv6$3 z-@K?1=kIzPE~oU(dWcY5U5nmq(OJeyf#f;K!|n@MMw2Gl820da}n@73SQnU&6j4fZ~}_D=Ex&)a%2bsiR%V zmzd}JKdQIvG?)NE0>j#n4D`*=AkA!c~VT9|0nG;fVD*|3V^dUlqauKMEP<%9vs@ioJPJ_+;l;c_l{aB2#2T5VI zH*3#b%QGtEWc_LARpp7!3i83|Y1_&2RVMrrhS2EiCsji<3h~qx_;w^zepM~_nl?v+ z5fKy6GC5eyl2w|Gl$xWbq6Y4=^6PBvtd^1j7|)z!rIdwTq@j(BRxhXi z3~*EwvwU2LpM_%)R_O6SwgaBj2U65V<*5)5DhwG<8rPnk8`c(?Kb%F=+$`aX! zKV)8|(4F6gO6PC=T?j3exJYUF7eiCmJ^%M@@UQazQ^Vrpbv0inHH%R>vH(0Wg0D*5 zMyz`T-+Yyq+0;WBA<4kZl7t{+%@5a6|3;B-3r^o6krx0$YLL!ch$g+60PoOW zZYsm(`L}N?;TD-5_7q1BHWymVN%MiNCr4|7!QBmndlv^HmX%=A;-$}}pZ_|sG$2uY zc)MuO>NrDEz(FuGca1f=nyo%iLu=>|lD4g5uv?^U0nMZwrYHaVa8EvS!ugVl>!1F7 z8S}Mum(8FxCFeM_+ZfrQr22?g6jZzckE9@qpN0_9F(j7o z>u#PE75CxXu}U*EjGR0*0lx99>t6khr7b7Qq3j6sCJq zt?utSXA9vkULX9$o)cq`CH3}a?R$^7hHr`#>jCP@?HhEU_20a*Ma!kv6?8SWRT&fa z8`NnKFyQBwkwN-ftwt;77jx13O#gpgH>(|Pi%&8|k#KZsKUG+PEcb+%%#7PKHKTy9 z@HNuH^xtp=&NoPZ@EfA}@pFSMBHh0q#Lg5q(>CeFIi}=YqDQD-cdue@fsIUc%qP)4 zITJQ`Ex`K{p>MlmpYBy_=DARfHC!(8@I32uG*?tQcvRM&BX2>Xs`qf~yirtyZu6-S z()612a8kZp?34$oG_t_fMttWYm~D))7)WPu(BJ*cp5C09qZY|FmqU#~#fgONM_UO) z6j`d^r2e<LFuK@zH#ag;= zed9e#KuW@goowvGZ^-zVi0t>wfJ5}bG9d1;LP-m}F3Vo&-U^BOT??&pQM6dT+b4m? zTr5nix#O8zkUu@Jqa@2wCG1AZ+^DhKspYJJ?N_=eSS4;YO^~%TBiST;Q%eHqr$s!r zJbc=9LP2!|YXclCp&aBp^M0HCiRRd!FiLjCd9|UHYGLEWeTglHo8K^ga0S~{ck{~b z8h;&+PjwCs7x#~uB!Aj+@F`#cT`2~ql0%AsYa!vlq&L7VKmUgR-3@Wr|hNyooy>wH(L5W+y#6>_as5n~VAIHaZ@(&n zCNPnszmXhCN5D{jE`;T+B3`=g3&IZC^H8OSF%APE>e#A#GeJaynNjWzd*lJ1V`icY zI&?M9V=4&H=6Anh^pAt&;;C|By%b<`y|K_UiBXrtE#xmcKmT)$i+;XTNdI}Z`-~+; z!rjZz*}!ODsS!rMawnBft5MxXbcmvN^%D_O`D8>k8O;*rA^)LTve%NTz&I zQtar5F8TEGd<{Uo0%)9CZ9`5d%<7_gMHr-dx)zKeH^q*m;y~8c2Ie6;)^6ZxiN3#` z(fCi}fRuTipB7@TT7oV|dx2hlfkIcGy6(MTiFeZ4@V&<7sg2Mig-~ky3)j5}cN^vI z@mt8J&JsX89!PuYqPrr4%b?os_Jh*B#bu5@`kZ zkLRIkr3Rk?#az#^AWBnx^eeMBdbd-h%3C5oak0&th|gYl4LF-a zI#U)}+TL7ssPRh?T>5SE@Tqd{W6DMP0_=veIi zSN0!7aD==vV{Ve`n~I4vc^M}-R#cxfyNRCoR%$fGJrCbc-7UP!ea<-PzK{Z~uodf9 z18Fxitpo$VW7v9jbf&m-#+oaf&pW4iCvh*A6VZ)0H>yhH5dA55j0<_jvw&e!mHHFo zM8HlD(UY@sIv>vv$qIHwfJs+3hebc2XNbM!ef=bqBMY=`=HBkXYcVy55jZ3;xq*dX zkmRq0O3uUKUQ_w8Ya^cm6JgR%=K6`374ZMLxldWj4n6}jH$LBce_8UTfTym#SZ{Gy z@x{z-;1D!%0sVA}IDjLEI)+*{vzTK^If7AmYpvZGc~yU6T)u~N-nCdf7$Q5 zHK;qM!{6*O?2GlLStE&0IUXI~t8BjMR%u7S-UjSN~q4L9@PWOhGp{r!ARx zzhIY|pS_eO!&*~WTHUrj!-`bN&**d(t)jJFKX!VZ+m#tJcQ3n@zLO8-6Q^Upq$Wf2 zem`bCBYAv^WtO7d{&`O1Ji5^qu^zrpBXlGXdPZ(<`u0{e1yqf z{a!cnRnBBlfve2!WBgYT$9f$Cg8+>s+fup=oG8yy&j*LQ^8 zb2Ff?gwV`73Emx%T7x!ka!9ND1)C*Wav&xHT~nEI-ywz{rs+?}GuwG;`i!L4`;#ogUXa4+s!+`Yx! zf@>SxU5mRr{OR+47e5ynTxFc>v-VnZ*_`E=MCdF);Q-Ql=DR7!)eP~PF|!iRbMp3Gu%CEL0Y_3(80fNzk2 zkh^TO!!MnWEe&R_VJ?5qReF>Pt?ZnJzt}l+p zJ;+t88qQF;su%~IZ|g_6t98+&2SyXty3WD$(5#~ET-8pjFsQ0t1Ym6PGosufo<-5g zx;9!`Q-Kg0da#j|c0@zj{i-d~f0m(kGKE2Yqq1@RD@-<&^&S`SxriRoghOoD^b2Tm zLJIk3VqHNb*eLnN$Sgi*=t?U%n;=oJg2g1GNVMfQ8X(D>=O>(-U@-BYe!%3w+n7Hp znMlp=FYAlPS&wBz#(rB})S%V=fi*UX$h(thfA>B{=-#`>fYl}EMR`}duzqR!QL=-~ ztG_Ly{^RoxBD63^LL1))QqH#>@AHFJn6o9ze3E`BcU_Hj*hyX7P2juJ{CfK~=eiF8 z{i$q!5evX7$)1Io70gG}GVe?QCH2KOm7(B4LA6m7WN*wYLriEJj8MK2hj%&JazVBz zf81tB`2aV?mmJyL9H#E;B?keq$6i?uzqRu+#j49c&9R|mLF#K*d@o_C1lLo__(lj6 z25%vaiN4ZHZ8P56KAw{0G)qsTS(DdBN31zre2KiJ8>nAGJ71)yRN$e6C{d^g-qWI; z=IUKLxvdTj=macUgyU7e+<*eQ^e~;RSNqiF?G(pawRm~{GF7lidxyi3h2koI(K9C? znU8R?{xjh6Uc`cUn>*yer17P((5ZvO)R95r=KwDRirMPoRFplTl)nKd^;y0mwYa@z z#|htKODZ-O?Efu)xuf!>kDH{!>0xY~d!)0eBg{p^*Z`_hXw%-U33mueoI(j_Vx6aZ zVd6&BhMAH{1TUm)4t|+`9Zc6I@)yL=TO}!(U9R&?Wll^yt@)$r8!(UxL2IN}3t!4Q zcH~%IPM?;ivtqTi8flM@I6m7VoHMh@_j!^j!SBd~i)aJC;PpIje^O3^OE=dJaeYE( zpb?egnwoU%*SZLV)c`C}uhs_c$OqsTGO<*)o=&3_#KoKFZx-FS%_adHX$W}mT9m(VeQ|v zTze?11gmwJ#`Ms~kXpV{;}nTwYoyr8$vN#t3Hp&846=Qm35b1}kr7~AO(%TOB)ofy zz5hy4UY6R{q$bbQZE(;U&0AX)|5yxZe3h$UyE{B2rY;OqGmC>=Nme+y-srq%1Mvjg zYAPj=jQo0}EyzYObq$hsOsg;i)W!TTA$TfkmcJf-KEJ1%Rvvn2UI3l5JAx_}&so+! zw~I=J*_gKM-F7IlOo?NE9ya{QvpFFr3r2_hE%`PEy~~Yx*2HgfhB97uKL1;^NFmzv zIoE_PG!yeQFCB{+AgO8zt>=MFtfIe|M~lEOpv3p5zH5#?jK7Oq#?@SBO(6NL+W)0Pch+~MuEjaoS~*A8+v zoB5RRWLn|<<7N0$Od-$!OFPUvVzp0j&+|10q?@K9)Xo2t;vU6m86FK|ebM`2e4f}A z%4JV%n|4#&B{0~MdpFMZ&xavic0DY!Q~1H?R8_0hkwijbH9r6O$Nul{`GkK7rCV(O zu{v?Et%lADhmR=l?pHpd%En-ez9-Uo*uB58{mjhHjpT~)H-Qk$Lgx;_Ih7N)_Gw*2NPv0Qr zRDR(<__|}7k_1{B&_KsmqC8>+KJF$-1X81yx1;*p>1bIC5L&bPfSBY z)6x;M8sniS8)h95OnifSwV_A!?^Ck_PIdZRDL>WgXj+=Jtk!fb3@u?*#{MyK| z9ZH;c(gil?nP#PY6ZL+Bb(C{UaW7N}As=633={DcbXQRsk7lrz(Uy=vSwxBUwKsjL z59tZV=uD#v?EXZvf^FB0WVd1I-&D(nrS~4=L*5s%(d*zbf$XfSv7g^o_bPpFuLVj~ zhSCXFxsDEgR{c^hUsLxUAMqFVo<-}#PPR3F=d*n{Di8gu#nM(hn2~P=!_pro|kuCS}!BRaJGSeNW3W^9&3^U!S^=OD(8n&R5CRmh^yB z`AhVtpLH8;nV6VXzjA|M#bc^?S48Xg>|t%T{}2>)ojqOL$OO1EG?u+5cTcdbYzGa1 z+PwR}T7bL6&YHHp%$|@D%b)bP03P@Cz+DIk_rw z<(nmSSF?II4CDfX+`=e;q$VnDGjLH$|ni9;PV-N(G%6mwM z%Iy0qByxE_ag@FP!*o$s`ww~^7wvW zVye_U!S*@?BUpn>5ZA(EAXngc_{7#)Xq5nS6B84s=4>ub4c2)hV;}9T z^g1PEUsn0$7DSW<6e!b=HpDdG>OYJg?#m-nQTMpMtEkQ>GC{0W$QV)Go z%8)8*mB`==O+TP=Ow^)@V4N?Oh<~D|5Fk@WOUx;8Nll2WT==GIo8;ojo687y=XD() zULyA6=%2x}&c60h_Ih9EKb0Mrp6!;z)l+{fKY!bQ3$)aBZ^W(>)9O(8s;c@J8aX!c zT$?LFymxkMX2W{1HkXojJ6J{aRxcWyiY}6K$o+-!i;$HgD3Lc(thR`5;{Rwb*`oKO1e=u4|{v$bg zZU6AO-?4Y)3Fi9t$z0?xCeOfDz~R^E4C)e0YerBt5l~Uiy9R&5<9A=;j`6^S^S6PX zJ(LwoLOs1IFJfw{J%mK?t=(APyoY_m;nv@}n1k<8=903B+^M@!%5eNf&*(QW=o%=Q z5wrqCmNpc-T4Nz?QC|)|)|FT@O68Ii4BPHO*mAV5&uOl5OV6en+;z@!1%6Md0+ zJquHC1w}kni4)Ws)5uPHk>CDX(i*{)mh_0|YKb5ipV5B)C*{i}TSV4*@WYJT&QF26| zj|8{9#340(8!9OX5K8L%z5Nz?(@3bUHLoLb*`?>!?_H|2u8d}j-Du;2ujkGV~dgd$5-Ef=7`|hv{(87F%)YeP@Ge< z?9jn-ziMpH&G~)YPB>HL58HYvI{16L+cXXiUBGD1Ew2HkWm@bMr$$tvR0tbnZ%SO- z4r|2Yp%`EC6wu2#u{drPC1#;R)C2wu--h39lfZ+(T!%O)IILBl3L7`h-v3Mw=cdaC zNu%xe9RC4#ejm8o7n8Mjw}g1SRUeE{r)g^tgI1OiA+a)yp-;*=4{R8_ z-vc`}zjRtk!1OrGI0n4oy_!*?brk3~y2401norDpr(Xya1#+xE)`wiU-C|kfGcIC< zHukm=f8)eE++$1!HADDVeY13gi&m1n-b>w&9MwJJG_~V7MyDa9=m zWHcbpj*+e z{CHZ@_qy_i#v#0{r=|JN40ReeYAEoU6W!{cSvyi5_w*=5_>bu5WzVNw1K)R~gFeqK+litWCsja~uT_zL4u9Mt2Mv}}6x>r!n<>ZN^I&&g zB*5=x?sZ5uY|ii6$!|g6kSKE1Of1ecSg+>U9xR~2%|X{p;%VIHotFw>hI3c6B6!z) zD}AEF2etL=#UZOPAPirJ8#M{0aVu=X14uc4fg_%I*K75PnI0xdD`x)9-mBXD>qixjqOP zo&->eNr!vRFB@94PMWJQ<9rfys1iZ!eyxOg!wmx8o&KPqsFloSO7`zCq)S;(p*lxf z6Fu)f5Ue?JzQNTiq6TXxzGILah)ejp4Vw2{<5HOTb6xc9r2t0Xbf?>gCQeoa0^riO z_#>o~+PMJB7R9w!A9Hz+I7ri#V1h^;M9SwwMGt*b^MSlhN-fNywfN6JYf{vWG*0JJx}i?|uMw@yIb~ax+ElZzG?2Ayv4%W*?CFy9Ca$VYNB8}O z0b)Q+qLDA2lIO2~e%L>1M7Px9{mlwDTGu^(*(~XoxJXTw6I-Sfg%p~ttC%e=mHJ*Q z3-4840cIv9UrD;C6wvEZS6FQ!2{5bKT;_G(PLViRcE+~ z-Zmt*MNRUJJodicmf9k+yUL!FEiTyOuQ;YLxfQ!XXqAs}STR;Yal1|wMoTdcqh?_9 z@%sJudivjvng=t}D^~)nf~Vpl8 zkj{*7V1Stfoe4i7wPTcm&cENgk*k2<2goLe{eL<4X5sLl$OM|ZcZJG`r>spC%<5l@ zNrv9Z1LE?9NrCAJzh^#w3|kN?+gw3hgG8Qm2Za87z&x_QX6OL4-jF%gOxUYv>$D$U4<3r0#K{b>rPaCpNCnE~tahekZ0Lab@J%er3DgcN@1%3@dLvXe z&r#7Gy=f5RV)=gLUAe+>Td@8`o?ghNv&zQd&}LAOX${{d*c&nBD!J0>hAu!%g8I1U zC#inaoEig(jKku;XvFRiv=AFR)>!2|5P~$8i~)Z7 z4D?DTel(sLP{}auFNUghpewNTz7m5G-<$XK2a2oRy!)c6x>S}e3eVaUqV1o`+-n?Vlg`Lm=`OVk6tdsO zB=>k_s3xAg@xry)WKvOAJi;AF8WNi~_mPlt|8%ox71_cK)J454oAUdONpRY3OymVZ z_Uww>(PuT`1m=^Ct&?#qSo`}`S5 zajp2@kd=I+92UQ1#HW}c1sz`dbNBHVH-@Cz&=aiXx@rcD2Dct(bK*S73hR&Vf z$H{K;AdwAUh4+k1S_2Vv`-U&@LP^wQIRCVTg?tWU>cf~fOB39ph8WhcYo34E-98k+ z=8@3n_G=5RZNm=R+lwRMP@CD{i-Ml7st%*^pn8*@VRNEoheNV|xovZ1*H0c0}u)ZjVuP<6(3WIc&+694Xp|^b)ygl&e2H zsoutpEJ)ExLJM77JzCM@0eF&gpSgE-@{TdPEp@2RWTQ4q>KO#56}KBdqgiAe7g$=0 z$Z%69T|YHdSOiA29LiP}6*b7U`}D^u3LI8*+efxs9B!=^~Fxv3%+O8*Mwe|{R z`%`*d#yuyaDH zbya8+2F&9))`8CkN6A6IShlpL4VriobTP+>i&bubfn-OsAhGdiH5^YEb`7r)gAvn|t!aG` zn>BrycsS>oeVl$5qii=Y|N8T$i_95XQTgv9)%m$PsE=%>`Y!m8EsBR3HMez{`Mfqi zmzP5+^MdV%Fhm3gT&p(i#5x|c`S2Cq!yKDg*qMy(rwj1RCTR|@mm_{MFql?6!#G3l zXlx$^3`1Z@h@BVY7H9zHHH(H~HCTzj-;X}cOtBIis&B2GqtJJo9I9>rNrPq6LdrR` z>Ak(Ii;`OEKjqc7#64fZQ7?gfN317gAGRdbzKmZXlx)X0sqML69fT?YtE;MdnKC_e zyrB(Fz)vJZ4LoYR@l8#;5*M#957&{MeAcbu9J!~|Ggetwx;C^jOLQ$@W~xe?JV9Qtzg3?x=>-F-MINXA3c7 z|A*Hyc>{DZm=0YjnL*{Npw?|^%BGU-1UGVe3)bmIizLBY3)gG*O1?@{OLYGL+xbw9YR!ZK+FE5w5Lvb}k+^?-32GFZ{zxsqPkI0}uaG;4rSBN0N4*=LL(7DfcI5mD zG#2ji)dpfP=75ZC_LqL0&5KMuew&P zb?ddpa$ypYOx@IN_xTK%Pk*_Ze4Y?bukvfq=yTSf<0=*meIR=YzO(unOk|_X4RGVE zF_QGdtXQppqWXqnossrGowombP`B3--Ub+S;*dk@wmDx=t0^Y314{@th;B1O4ulh% z9YK8Ck8$h}RTx&)o-{-vBX^NA792?K3@^-q?|VQec#u=hgU;i1_MP8pP=#YG%xn2# zYz#EhpO{zgM>STbNBN{`-N5BIdxdIAc~bF#GrC=|xr)U3Sb2y&*`hW05o7gb3`FSf zT?5}ff&=)WH{*+4XZL^ht0Usic}+z4<@sh^s&Y>3k*(?djNGAa1fWB*Xe{2s**qSK z4Gm(<4kKa>@A*x#vr+0RVF3~;m1M^;Q_`8$tJ&A1|E&3Dg?P*VRgGT+eArLK1{?i*kP;SpRbBpe*b|ut%+(HW{!2)o>CV| zygk4)xDsWR(gBISxrZ@jR)t2P%A}jSH=Ld(*#Ly>72iClT7bh>D zvq|hh@LzKS$PZLSi951<;>%N!-Z}|Q?_f`5Hn{rW`Ye@7oEj|lk>_#T5$j!i^^eg* z3)=`!bknjP>*f-IlJ{{_){G?e1KOS(9|-zr&RTmf*)B=YAkj)z=wN{IlrvZ7-bE|1 z%Ma)t5Z=GoHih*2?B?jeYyQ7d+B~OkAup6*0cDKwAV2}3L|j5B*evCaAEBOGcKDm_ zUnU8ODeCC@5nA*(i2RmkFPBj#H+eP7Issk=rx|d(1LNe>RGL>jO=M;ov`Tu_7VMfn z-_8YEUU5HP6WlzlC(Dr`=z)PXOyP#p+VkabfEN8$EIm7V_)k_!v?Hv*{zA;uo-+SD zcuGar8!~9du`RZcL9xk>{Re1r*VbKBILUKksRX;S4QBhiy2qLurz*mPkbP5h@2I9*4&dLh^ z%x_kTTWQ450ND-Zo3jZBw#|L)*!muIMUQy~G?PGLmgho|lp#&_;s?dSUZ%`VPpL*}?WjlP=k2MkF)Uyv+@SXiDI67@;z z)Ck9#7|(CT1-FoDIFF2Lv*&kxK$v(DsF*LS3?{K*3DTGjKq)@h$Sm}@4Kk09i1iy{ zw3|`%*iR@^_M@lTk83eK*1U44dlIi*%?-v~R`|RQKu6ZQ;&k3t zIh_58G=}RD<=S23%VT9qMC9`>QiM&>DV1DD)W=Mdg(EXz`GFK}OZ2sW` zEU9`PBcYMm!XQ!H*jwg`nM}T|9wFdL^qd|GydBLBl`g z_G8Ce7TRg&$aY!paQ}SivNpIPUXJHX*Px0nmZQt&nMjyQE4`WE1ZRy?-CZjBxOZO` z3FX*xg$!N1uRtm+D!Ly0YBvs84^yJlT=hpd%u5NnnJPVqwkLMKFwX@zb&cM`dBWDK zme)ZopiTJTlRqR*@(Lw)W=ZAATTl1#qt!&3v-%hNyqWch#iFA)FxmL!G0K${|Utfx5W;fTIwD!>kdW6h$qf zvLV8Szr}eK!(qRM6uBbWj^fie%w3Y3hRvaH4yeaV>KvasYo(n8&F-PTAr96PdxPXs zHfZ9rafp6VGoY-bSjd@ccN7ob@1e1{@b)W=_$Tf_fHP448_mx5FtQrW>Jss3Apc*K z{Bp-f3MNaDmBR@HrbPeq!sk#F!;zGR265ga>KIP&x+^^MMd{dTbUK{UmzY3 z^e$Mp4%;`0^NsEOKHnrp%gsh}NwgG=1f4)HrrpaY_Z{9GeqQB2Fp+wQaK3rwl&i$$W6oA~@^BGBo5*K4` zcKWWrz1fYm&tgc}=8_dG_xlHYg$}M$DXTSb!F8OTxe-6;0d0f}6dz*)*QCM^bi!Yg zLw_U=eODS8Rk-Wo9|Y(+aUtE_9%(^UDvps-_I%BfD8>l4Je~p>(#G{`Cg$*m9Cnkc z0+lNlQ`=NbQYA2IRa<@LBpz$u33}MGoylIz1`t90`pq|QQRVGsB4ghAly8pEDLX+$tn`og#8_6 zI`KYFZ8=0t{*Hr7!Cfh=y+c-(x4f{PFH}TuGolbPEA6^QVvp4&s2>B~(YL?951TPw z*D0YIUPzEzuYi{FIEHpCvZqeM0d{FA+KP2xa#H5&D-49}*vLoZX1F4(!gwqDOm3nP z`F`F1%g{S=QAcRlQ(Y<|B(AzVJ-m2S0<|9$%nSM!W++jfvY05m;W0xnuWt@?&z~D7 zKW4kSm-jFF z%#Qg+mmlf0`g_UW3!w%iRSIDWno9LPvWcFeMau>%ihXeUm!IX=o)Mis9sT7wc?V~8 zLJad_loGfqr| zz_@gVk|VgRx*F|s{3Rzso^s9tXhX*kkQ2SQ%gn={)FF~G?#@ecTe^Z7Mj|?PMwWts z6ly`zRGL{ZJo-a(7x=o%n%<+}x7H#RLo(i+|rlgNN2;_O)7NyYO0{A6bIChRG;h3S4&=6(s6Lutq; zDbk}F((M2y2A!f^Y5hsDx5HO*ew_fIDZ0n<2!E$7RnXlX9qn*4$6y~SIV}hhHYImm zWAdR@Wb?Q%YJSNTpScO0mawl!5yP940m);PR#rJFRy0!u4W&M?T#)fce%V(agbM9Bd%oONb{rx4)7dotG2jM8_ z;lGzckULkSL~audynf+?l6~>o6tc%Q0p7?Aa;`6C*vJ_~)5KKCJUVdlX4p=8fjgeb zYpReA&XXUuD+-rO)OO7M9`yaM6quGJlE6mYI2l}C;%Z;}clj$stK&jIZJ^QU-trF^ zz>fgOrF_21E`)cL5W{MCgGf(wpappKWHhZ>v{3{MeUcM7$rInMXsTLx;L(XXeiOWO7=`-&4&t@j$ zzXSzWD186(@h=Hue`K}sJWh|EC6%OX!V_|vD_Jgv5ndIxt1YoM7Kd0k5ec9&gx~Vu z{&rPq{^dC>&evB^5T6vybr2Hfd_f8xY8g zNdOs&8ga2<>-WjammdOlyeoiUvMb8I9uu2ZV%AnC!)IyTa%jL04*6oJKq}m+m-cIh zP~6kiilFI6E0g39k$iOhA0N>C&H&6bShGY?SoCn)JpD5H$IxX#96=aC*!O?^jFdKi z{~r!cGXSOFghIAu?r!IpSV5_j+)<83t5=NiV%=ROAH_19tvr0}a1dLNu8=JC- zCm8nw?)Zeu$!nx%nWRhSdcr8Qv!Mk#s-!h(VW)=u*$MNy3X|eb^#Z>d*SV6nVw?4W z$fvt8khWD`54`hnYgSZ^P$<=2o>aMS{7zY<7_E5?P(d<#tpS#<-F!mF-FMaV@+7INTN(C?$&iy45K^!EJO-=_Lz0+ zS6AoDZtKl?2HxqPYO1V5j6`@GgJeiEx|2kSQ?4kek2Y6;_ALG~Akg~{c*W!-@W?0! z-HjJJ3WOz)25>{WNM-eGo@p#%3-=ul<_N zcUUz-O}(W2qwuSxciA_J1F}E%I8FzYgnXKQbD(oI+;HX&*VdaWrZf&a|Cd$Fs8_AT zwJlv=%o=v5%Mrz$au+gSLK6xLOlr?h{Ib5;@FcCDE;0ht4cPy*RzPU_TIeaY(PHj? z-_hI+D~PTkCGonOP4)!7@?UWS{=gx8)316MF_VJMQxm@In-?g$Ed8)DFy&35_y4>0 z0a!cs3?xKmRRUbraO4bgLDL$hMl=6uz)Idhp0YOWFkOZ845&#`>)7bBJ@@@4G6)N=&EMijSPhNR>++ zQi|FqPrOA>0fRg=8YF;2X|f(<>~9|-K7n`r zY0as%<^q7Vf0IoOAsJcil+s|-rK&Afl>mb+2HNKm#1h4%11^mg%2dAz;2PUWH@H5N z%@dn1D8Og2xNygStl5Wr%3)=6+eJBG9JgXCKzalZRySoK!{`ykv?R@)CX)4Ja`cZx z9i*GU`EM%1v0}Uh$xm$hSGud^k%dvk#65pLI1Mt;AyDHVcKbm6r_!#Va>^WkRbI#T ze;<{ioxh-c{+%xf{%^Fz^?JwcQShoIsPoi=d!l^FEiMNg2@EX*JO7MzwRRZyr0WH? zm+vGfr^RshapBUO=H=4x6*tlqnY>}-Xz@db08mgnJ!o5*u0zsHxr960aS?V~fo7X? zO#?t!3~f+AI;2z93EKU3a5399uMp|l$9*F+{}qUsY0572MI$FZiP~9(% zRn2_bq@eAOM!&|dD_0J0N()6?d;1&~d+{ARZqHklR0DN^TrQ>--pq4cTX$$#C{s%$ z)snRO#7u-qwQWN^ZB2H6e5u<_WV%={Krt>5%bLPE*rP=aN?8MaiE^T^)wmeKC@-Dq zyCB-K`DaTgGyhV6J)cSH!DgM!3xy z5TZH3n11CiR^Lpa zs<0ySE2LEoqstG*af~f{fq*NOvM!3L`ClHEd6ADR z*fk^eBbgzglOK&Mk38nx;NdO8-?%nSGh(yBh8BzXrmSjZ4zsb)7HJWvJaV`3?dQ7mMB{d+{UcRbT(FKWNv(o^a0DW9`{O94 zJ=kGZmexM+1

CQ@LKdC=S*l)%Tc`9e1j+9VvdLfI}N;WTId8l9kd_eIsz~Xgqum z?-f?RL-%GQ4XLfTdi_ZCu=IQ4RCa`q$6s0$^O`7U(GJC9Eg@a_MuQBot>U=tg|s+E zcM@X#%ZYX9a>l14XaHzx-jwV0rQf5?o-xK<65Kq(r{H{(byvX%DTvaV3O;*s0+O$S z1#N8?mpksd=~rQmreMEOvGuQv2PF4*kFWO41aqLXRs-2B;9YkGNc)Hu5_RF zNL|C#8zX-pf}gRjM^>ZOus5>I3^h$kq0l+6H7ANYiIjDThq^PUpYGb(!)-c~ zCT=$*Y+gk<#Qd}kKuAXC-6dl6ah;LWos)|0et8r?ZzVv-_ry+0;vLha>4JI1CLR zaQ*uWi)~!M<03K2#hgm>o6}}wjMQ2$H~Gy#I*xX)#+GIzmO)ybGd-n=^U`2{O^7Lk zLQqw3O#Sze{KZwGA0l#M40W~e6at? z3es0;-d`pR>)5A>4*P&pLZR=-fIlSaCHr3FzC@}az=1LQsZd{V z@E0rv#ixRGjL#MK!_G&>KIu_{nwRlbahsd^47X?O9G^5rCACCr>M_@!0$FY?CBTZ| zPb}RUtO(E?w3>@&xr{lAJ@b= zQz2cm?7Uk3tt)dJXIK{ z8C(1#*p;gmS9JC_;UH-i>1v%Z&V%HZBiyEAPWboDWX@Nca4h)O_#=EhLlxh;hUIDc z^k~6v7-v7w%;}xtlbHT1Q&p9hSJ=vrHOuRE5!t(fXMN`tbaU4P4RSkYZ_1Z8%2e?{7LL53X-6h>vMtc8VL#YrQpPB4-@gV^X7M|&N}iTWZL?75_BLUnHGY^E zNROmGVV@7xSR}^Lc+Ix&=?=v%FjdxOVGUo>TVe=K9N13WYiH_tBBnAZ6-KfV!hx+q zE?*clA<|{EJxMOdGx`&H&`9*6=k@H!ZmTq9x=V*kd<<`d0Q{X^^&P@;+;a{6Z%MI& z85RwH^FLoXSx5M#Tr3GGeLXN(URFH*oq?i88l)@~V1Kk9@zBCK+f4-U zM44LxlByFyT;EHJjO;s+^hFuWFq}>$C@5w>rI?*7k-=JM*o|ny&$z-{%tkTT*LA?u zs}ZK$`)nPnWC2XtR_xmU!*XSWnsaB>scwa~1*6t{Z466U|NfnO{k30ah!yk6;=6Ko$*niN$`?Hj=tN;|+R|wB;!t3~`7ge-|PJ&`0KwcMe#FhKbmi_9! zE~djb!h*tKDw*2+0L&0Idx)2c%E=fGf>*4ki6^l9o6&E@Yb~YH*EN61C7mNF21r#} zvi@BR4d64Xhe4DflZ;A~cM5l4TLXqpG8+ww$6?AoSG)cqNWa%qf-*M$v=O?zu1z`xJcTVOf`M|6F>k%<>A2#eYs`40yL`$(LOh3vd7E2sxY1~z zW`p`twzb0N_dg)RWKWhJx4`o;NwIub46{HRf*Qhr9qNsqcb}_Z78@BTtkv9rr>N_6 z%s)A={(P+tT=t1b+i|Zugs~qH(uYH84^&9%*2RKTxQd2&2rUJoot^DC{~XbY!^t&U zME1_W<`WpLduYvF>OnRqi@RXSY35r0g$NoT$@EmzNWP%Cx_qSd(fIl9UtwJ5m+(dV zxhXd7$Xaf!SCFQ?sX0Cllx+Hps}xn9LK9_gj}6X+xtk{NsTX&{KNkM&sJwfTY2#bY zL(2{tc#oLRpnc@J4-lNjaKDG57POI4lAzqXULS}b{H7Qy#+U`T$RR>K_x%3K4=Xar zuL_7L8f|5yuD^gCs;U{;3#cZyv`fLQOVrXv>=cpwbk$OgUv}lGd-o9v8}w&iltOJu z2`;0$2SRXkFw!<^rBU3jJVFA<4R-;^-(4&L`Yfw1fWWTYs)ZtEzXQ&|KOHSLK@K`? z$2XVvBXYf&-D7go2n18k5Z~-*4iPdFkBCNOV-b0$()|i}I3XhgYqp45y@MN%K%<=E z=@iedWlyDU(Lu{v)SFlR3L~vG|2K*7`mGs)pZ{9+mNvY;Uu7rUo@t3QU2Fa0Biw%; z7)4h3$c~RLXQ6|pDk$TX@q2B8p-tnd^2E@?%r}@oXBguq9?mahtB)P~8dIGd56xDE zeoPn&%ubz`Q015vD~E%dJS?C#Z@B>XGjySXbpD`Wn&ERkbL#z+w-k~Op?fF9%?6!R)c>YN8a^PRS*g0H{{ese|Inr5w_P1;%Fxqj3Dl>D)h9${ zmE+w-+3Dps96kQ?4Hkm{MWySxCn&bBB$h8g)g!KF-)S=l0z%@|wOY|GLrh7#cAy6O zwe%5XX(RlQ zxBytJjEAJ(8q}o^q*`^WWgDg2L#Ma2u}7O5>SXJ-E1{t+&` z=vdM1n@K`iu9-z%pNjKz2iodLn?HsCQQq1;dkS6%Y(<$lV2pO?1&&aM8)e+~63Oek z(4QNMHZ1S4cfg~AVi;d0kvz%?j)dxjJ-czq!o8S(c z$8cAA>%##w(bbqlooW0e32*B{KTi91Dx&Y|RZqN@e`rF!+owlD0xB*wJpSirQiLDO zmRMMctUPv+qWvOpaQu|tuJTe=t}F6(FVZ-En`EkJS~IYm?26+c`znMe&(v|=jUO?E zZ$=rA53bnDC zE8WdOggTxAV%_vfof$r719P(2u#_hM;qMoRtN5me_i-BX6-zFk*fj0UvjL%jRUhbkTpGKK><|S#3p*zW~qb&gDEW#L0 z^wWj!D8OEuK!R;YS;`lAm;QLpRt%cj>e5J)=3P|^h#{Px!5`io`PNwJhN8Yv3+#Lm+ zWOJ4Qi+GD$(xOD`{t3Ip^TFS-+i%bl6Pk3wA<9F+{H^4#%sKYT?MNx(R#mJnf(shX zo)?(XcnjG$w*OhP&!lm3_L29J)a}FG*8Kouw(pq2|1e88{p0j0d6pZNVZHbN+!*?S zwQ!w+UzGKA5Wm8i;9Ro9>gCq}G|81wh7k?<;;67tR(;fz(Yvni@OG%2F2ydrQXTjO zceLU3DCKdjTaB#Vtp&(CeqmM8-ML0N*{@89;yN5xs1fw9m1#n;ih>MR7u^YdbK1Ff z&zdTLoX<OBMBIFT5)?_xgiQ3_w-RM%m={*o86WVVHt{IMyFn+P^ zi#Lb*S-)D*KVF?ul8Lwh`K&DSPIRCwiSrs`XWElcL;X9)GVeRv0X((K}Pac+6>kkjs6_yqkFU)1DP4&9!w!SxY! zs#*m0n2-Ck+L?9*?rqx|+9)z#{CB;sBv$2AQ>S z!+2H^cUMWk?SV=5JAn`GM%XxF#@!hle8h@9+W66mpKQ)l@3KM>`q4!*QOPMU3hWoxSkvFum%|^R1UwKp z9Av~wV-lydPIt5rNId$*3~K=B7IEBmZM#xfb;)k4#zB;w-lw*RGW&anT!va?A?BP7 z2p&K&Vn%*sW5kD*q=3ea4(e#scLbg&|ML0)`TS^twG#aWo80m2r8;SHIwY5h#H$aV z9ZfdmSa>SEi~MhD{qHpiIP!wr>4r!Qh?e;mj484Nb~ejSWpfkycS8Vzg;KQ8LJ$-m z_e?0(`q=jFnoCBTCS@#UTvEVKj#bXs9y)^G^99nOa3QScA91xS5cZ! zdBtJYu|FG^VEc>;FC%R+FX?-(p_u2`7}@XCaY$$PKA$M){ud|g@d60eF8XMR>r(FIekN6S`nYUoO|lA&Cb=&maitfJd}- zGPHu5n{1RmUq(3D*irYekaFmEgX^wPv z^wn=HNgQszf88Be!v}P%ZBU6thieQpa6wtbt;2#^#Ee?xB8Bx#THb*jGhnh4#Tcx| zIJyrrLwYbAO4l}h$KJWQgjZ>n*A_8ernc#!$E`Wuu(@Pb{U21(pMBha)+)4fK6aT( zZ|d{fL1~vm_cW5pwjbUH$Sr+3*u&;2xJnVhhPx**QXev+a5{0$2pAzHO0j9N_rw*C+CHR8S zhD1%Iji{Oy#ifS1je3a020t6;-Acv@@ud!yze5}+dDt2 zmL#b=-BH%Md zdLu$j{`MSHhdz56rL-7P4G_*v6j7~vK}!@B^HxN!|u167@j^dD^foSHY*Hx>Z8{tSzS zNmPW!8rz--c?=p3F+5aZX1z5IwwD5`D51eG6zIdz{~T-Vvd_iWw!L#LJfY6(Ql4NQ#0Xbhl~6{qk;3b(;JKy55KnA@Wz_ zVwg@TGDNTu_b^hSF|bg=58dgicl@87!8vqjf*jd}%O#DWpe?jb*mpjjj#G@c--faS z01!*5d!gO*$Ju*nM8lxl?sLO~LPF*T5s5?meZcdp1yn<|zxgmT_jy;&+de#egKc{% zKm;Ru;6p{D`Az_o)00q8-mgN;*{uGJo{HR`HRBV%+b5--wEld@7|&{T_u3z!C}WV4 z|90u}hMYdh-iGcHtB& zC5yx$i5mh@==WObjyWL|=XXp#kJom3#hP9IvO+oNpNFt<$9 zzkQEb8|2@5jJU%yPLQk~nK#+so9cYaIaPk5-2 z!H6-8O;CAeN?PpZH~a`1R1Q2$)Ia#Ng5pjq!P}uS<0JSa_#yj!N+S(~30!!G+Oe{yW;iBFx)=`~rY?MgVRC*895bQ=+$0HR0~jS*<)IJgk@+TZy=3_KG6>930g=mw zdhzJ|KAZXhPd$>}ANyb_{i|6G!yR?-w&p%^KEs8<{j+P@jaL6EBi?IYQ}#wXxau>6 zC>v}4?o33wlqzz(L7L7~OJp*3p*mVCJuUQw;gw7}!ddWNR*c@x;9;{LuztcKCq>3s zpcRraMA!-L+8g>=k(UZ_SNKXplV~Zl=0--F0rR-mG6vRdX{35LcCBnqkr_g^owUyM zqkYLZl;5O|MWwsSetPEYgc0DKI2py%FC1&4?AK_tFazd!df)s-K{B6IyI0z{lhN*UunHeuWpZSbm;P(h71 zKhMa1d+e>ZZQRRrbX6wzuI^cM-2@xG^ZW`m^6a_K_I~XSwIiPE`(YY059?D-fQnCc zF#0QQ{G~E~xZOx6=O0@p&@LR3_9fPRWEOwr2 zt47^V2$N2!K=CAnc43Tzz~`u0NIDzm6Et|pi)BV|Yd@BxP4u6Hx`lDA;re{Q6~3jg zXcF_iPF{>GCLs%$RjbPFh)Qx%gduX{Z_kwVsa*CI1CO zFuNij^AdTFPC=MyHioX47W6W0vVvj*pFg!TFKSa-kJ<{>$99P>s!r2mp$m7==)eF$^@Ui#geT+jyI$_h9} z&6c$E8#V&i;5WHYF$)t^YJzG1FyE0K2>*=UFJ{Yz&S;~uz*=G+V!K20X3Xi_J=dHX z0CF^`r+w)#pIST1;v7o`#nNg^^9)&zes~jkOefyT@*j9_6rXMvVp@K(_>|y21r^fl zc30~7T?UKz%Rxw*BkfL?kXKw;{_Ey&Q99xPAIQivVK#2}{0R1qOXa4dzd>dCF0^dz zXY`~3Mw^30@%B1Ri;0NpYKE!xDod$7F(1}sM0TmYnE=%-`H3f9MxYUd%u2u;?lCB% zQy}Eui_-MlHa7d$KmIeEVRvtCHA56M7??CUkzh(RoF2tvobKWG3zXB`dT$?0L}H8w zZ|5^-Q;xG4-mD)vVlF=kA~4%;4K+f>*ZH1fJT`h5YxMTH8e3Jqn#X-P(t=Kz_$XGa zlV|t2$zrNb*}z>c2dp~aE$G(K(UpVoWhOR9GIJx8QyG2Pgz$ zj^*dQG7ulZgsw`h*dp?QlhIhK06QBs3zagDSR2vwEO{BLN`Dd?63q(TrN|K0xmA`T zgwP=9=L~v|ak_s4boBAaN9YMeIbSJG4Zpb?+W8h zk6E>%1^+_8GXWr{Xx~ATm#J^eE=N&ZbT`TJ(ByPcrU^J`zl(YF)EvOlB zOHEeGv&RC+)qNI`yF&ej11Hxy#whSs-P2>51{1Ys{|xxzN(21)k>6LAiEQtXXNAQ# zmc66kh1W$)9cZ*zu2Y}XTG$|V74~O@akk;yF1xvS z<1GEL7{^Ugdux@wtiY4vHM$&n3NDxDLo_I_m2w;TYkQ#8Xt>sEcG;|&B~xqPJ$&d{ z1Y+3dB5l71z5owW#mzjoeHx&Wh$%VCkj}ML@fI9!0<#(>tvxw>qB^`uv#hwo*b+1~ zS6(yiU|MAq$zr#z17+Nv%Ve{|D)5~Z#ST#sWUS*34;U+CXkDi1;#(uHq-T5aRX}9S3UBYrRDu2lmcR!I%3b3+M8P}1 zD#J9jH!{X;Lm%!MetdD(bqrS-nQ`_|ZBomxF{xHN>J(eg>~Ox;S`_4UWW0Ji$jaDp z+ZLolwl^4}(R8%dL&$Q&+7T66p@h2>WRXj1bX6Q`jL-Unhna=_YXf`EHjH1)d+W0w z8{9MvXHjfJrMed{l0_)W!FRRTG`i|@XtqZ+9JyQbUUh}wt`S9|&um8zB|~tNf9qlz z@ZlmXcEvW$RE;~CrGeLjw_`&Gs|=sYzJk${*NdX3vz69EBW}1fgz%{SUWXETy4x`l z?K~nE+CpV_1IS<|nc5gTy0QXoS#4jRz*@f;=ghpA{zpSX^=2Gfi;q}IW;#)d_pJA* zhE`pn+qjb;ld>{H7hzHMJi~qr0E&1^k$V3L86f0z^NJqR6n}EQfTHh|GH!Ze>YdI* zNni!(bsXJzUcFhWvtj@BO`qI*P-{I5>e~AAyplcBm?)$zUM@Yyo+#CV1S<;|0BD+5-5k_I!zhObw zpEz!M(=RVTG(JUlO8TghRH(IPfO~ZK2=_tx zp2jr7br0iN_-ap8KqMXiMvAT5Tv9}?fxwwH7h}EaMjT<~lKn(d*u-(jII?~u7Fc-r zc#v0(2`Mw2w{!m&KH;8W2oZ1&e@ew3kRQd1pSr6|8y@!+z0K0d?r%qRk+HdW_m5@C znM#Kl+0D%3k@?Hf4}bQ)6taG9@MP?F@&0ZyBS_VgL0~j6?a-zyA+3&|*&;l2hiU%J z>P8T{x1%5CFe!K&a6OJZT7_TI>Y8A#Y?_&iXSu|K5Hb*l>Qsu8&hJ%GF${@uTE@!0 zqfCCn2N9U7f4Tc4_HrLg42@#L$!#^z*09jHyZ8zSwHDq%x!shLXbLgg-Bnc-acmRM~hY7 zBGd^hDX8w8$9?EBB1ttp9r>o=eN>(V+k2P!H>JuVHqDhsdWb^OrtO18>!J}>Qls-q zXnqkL^6wYl(kYr8M2Z|XkDuOfa}>rtf!NA>QQhi!r9xtZuG+I21nwFgdswx z_exh66Z#UCEx!Z&|HKG600@{I&MoudFr8V#>g!X&(EwMU>tdS7x^L=v>uZiMbh|A^ z#OxTO?Uu88UE&{DcK>J?@Ife81*qdbm}ZUV1#sb1uN!o{>`-7hEwxP6tSPdK zg%Qpy^cl^JGf5H(89lggg1oM>speL^W4XPtT>e~&j@=x-MZ{qQ(%jwnb`69DdOsSI z&~p7y+qV~xchbxJbX~tS%+;w0qB{-fPzG|7GRlfxg%6ixH^(vSPbef>&HnHb z{AT##HmjX;sKV?HnC9pEWi2^LtT_$;)g`!=rT5~5IW`v1&-7*hl z{OW4}!F@*Tx*-W2%R3Fh-nk*+hh|P4{;%EZvY5iTfE~~@9M>nPoM!*J2tDTE&H)(e z5}cxcfqfdDXNI(QD>1!*MvddAN-Kjhma{6FX@7j0083A7GuC}%0uvk4R3k0Xg@TpX zWdRt}JYnFjJT*XL;9w@3!;|=TlWV?LO9x`y<~;Hm2-|_5@AHi0%Sp#r*zHa_fP!Qz z4XvBC6n41y?RT(I+)Ts)EB)0&f@Ct5y!X|;3zI!mnEf3lZkFK==|ORE11FQ2XI~)9y%8RTb@z*TXP@B*UVZ%1fFvt*HjBM zEe^)Fm^mcLCQ~b^EQ+PNAK)3%OsuCVzD||x@x4qw+b9WzIPrLT5B#c|%q&+yRhHSG zGbAUTX>BXZSIx)|hmY}aWL25ewqzfCO#vABrpIu+11fQ}Z3d|bxY8Ma(Id*CG#xy~ z&-`HpQr!2T>QIsE=5e;1_^K1{>TNTxPPC{rZa51+eFx)t>V#bI_DQSVh`dU^lqTO7 zSBxsp<`0_H-BsN$EX&<8k9PX(FfRC9NC;XN+Myo|s!$7GC-CYapMMUG8H_+upS{oZ zbNbico9_=fzm&z5%3K>6{L2uHwa?qluH^eQbnMEMCCw4#LX~<6#uUs)P4&&jDCs=n zP1A+me<{4d2Ed$X)u(^AXQuUUTPQ7-AuHUDDrI|<)U3CP&NxSccCAL7aRzxiB?QE% zu1AQ`<42$dFSum>2I-3LynH00-|+PU`J=fH(YgfYQi7a=JAt{O_s$1);G?!RJp`Iy zoYd2zOq;>PugyPAh~isCO0MMUnfqn-3wd+UHbO2F%L{aZ5T_t;Y*$F9BPxsNlq@zoTL+A;}wTMJ>R&S~e*AZHOXQ!^=9nleU}7M`DU^9$xqSw96R|YPH!8hX$ru#F*G4@xI+B#)E5C_NQKYE z8L8E&?Qy+7QLW|5=JO4b&Kz%jE71hqFd#X-Chsk2EVBeo$J-S@0?!lPr*pjg)k}~y z{j6mkMq#1B>p{iFS#>nzn${4vn26tC5!FDIX^Y+RhzmAyH{60#7MD^mdIu?-ZR93> zR$R+6d6cK~v~biW?bLV@D$pD02yTfiB5eQaI>G9EHT6SaIK7Z5d5(=qbd+bjeWu+u z$rQ60ab*QBFCCu%5YQ(Sp=&&Ed7|FqBMn}XjMIDYmv`I|`mevdA#%-<16V-UZN6>V zO-lAnc9tcbZv*PdHIfz6(HXw}W!ZG1=%6}AG?Q~4Msjc}bT;|RMals+^pJEOhia6& zX~^_o?Y4>JiBZ{m;?l~mb|(?*a`>-EmmoSLrP<~biJ(taW5?1cSLzi0_jOi`9Au?mQ!b?L8&S>?i1J!UTbYbRyOFx*T@G|s*Py%x>&b&tZ z7m+6Srr|S~n%zpNa`paeWhp-u`Qj`{c%H~AtE<1F2INHDyku+0H9{4lTpkq8}}4REcW>Mk%wA?oJ2c>+ieV2Pz-UFHcEu_C1Q!NdeEQ# zRU>ILvY85yee#dwKLu)+83nNc`brxpF0sa|P43gsc4Ws}mDE7Pe6XYeBAvTGX%pE2 zcV8#OdJS~Iw@)@io3&??^>3|~D-RsNb&Vmbkc9LzLqnpgL$Snz`v9Kpq!Wd}3*PbU z=yt_kLsFZI0<~q_mKRet>HD?*KNvT)y?L85*3kZ^nxN&ELCqkxF%GBY3!G{^?GMXM zVEv8Hw~h(IE7iU<^>2-{v<=`Rkp<=vIq*bCT371pRcl+2-dI%%MLcbe>Cdcl3520B`<&uii%v%z{_JBlRevzl_4xlDxziUWS^8_w<`3zD?R9BVIEEnu)=)B76a5@{ zlf~hbb;X4~F!6ZXVbF|pg=626x8cUfr5fkgXsrZDd}C)wE^_}ECw*+L_td)(hrF*V z-m4z35HMVP;{L{1{dn-5*HBrv~{Rl%T z1nOT^$)B-QBltHC2PKCOqIP5L9#Y_^W+Req4h;{v@rgg%2z3N#>~S&G97U1+>Zs-D zg~`@OwhuBTH71%=_f!o@ncZtZIb-evukXG=k_sMOW8oo$3j|ogqM=Mm-a!MjG~xjr zMlGjS!Ie0Kc&Bb52XukPWU-ICaO2}&&+xxdgy05uoKS#2AUsS!)|Z$ zIfs+shsP;h$BC3!U8JN2#_>FdKY~Wt-DrDLs1P{rNzpY=P4MykWrfZ6-~<-9Oj2)> zIZAmoV2cYHppZq+E+S3%X&>c-?|Qaz{g%NV^GFsQR1}4Tv!I}Vr;2RA)<2n*`Tdyi z?Wv`R(YK0xNOsn;Jj?gtW1{bro?)JDUls0rZ-EswdPULvIyxbb&?&2bz>&86>(@4k zhFcS?$90#BNoXt;liOy5wR>{S?DQj?EI;~47=pn>wgs}Y9eA`i2}jyhx)*X;eq(eB z-0Z`GBb#LxE3aQg@St5R858j%bDtoQ<%DsxmyC9)i`SWJWJiLj(00VqD6Dvd%`6C9 zbCy;!xx@evnmhPBKToAbd+jPc6b&aw`lHk@a+(Y0I6B)9}!+z-O$X&vKeV zJ$(B80-GbD0@g@-+|D+}!j|u=kT*Yx*o$0_TvCK+th{4byRFDem^CNbq!1RVd2>p& z{<;Ic@Lv^Sh;y6*q|3bT_2E&I)98>^W6wZ9Te5FZ(cX%nv>z@ZKFTzUmQyi8@s+i( zJYhL2D6Y0;4*5*b|BmJ)*-dX#s5;wxp+??w9v8GaahM@}%=@>cOrRrAVvnLgFQpGkO2>G3*6>vz16M@Hgi1ZW3BjV5oaVl>6X? z@IH36s(0aAS4bo>r!9iplE4Mo%-z2sbGl6&ncu1FG+(aFfdxmpH95V-;Aq>&uOpF< z$K)g$Ar``6gmf!JtIK7~$U4WH%cwx8(#)~h5{q#+g2HsrCyfi_I~*FWNjT9oJH(aN z+XNfx%@;!V<4LH-4*Ag0n$Tt`?}HOyb)nEWZ2J|@F8;m52K05&gP7Lmpn(E4oD&b1 zv!6gU+`Ek>#rsqlDgMvTB9TV_C`}+3=UT;|S>=Fx>AlH@Yg1b^pltf8D7Mc^@+)pYYNR!aLTYR) z@L>w>ps*hCdY+-f^@&w6K0Y=s%y?;NQf=~y{J1?q-ZOreuW$%0-+6^_B)F&5CSD!9 z96y6SgF6w~u6VNBc)ZmL=cy+i_xLO*a>\=kg|wBQmx(i&wX*!>KtF8uifwJMVB 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 + +// Ackermann function +unsigned long +ackermann(unsigned long m, unsigned long n) +{ + if (m == 0) { + return n + 1; + } + else if (n == 0) { + return ackermann(m - 1, 1); + } + else { + return ackermann(m - 1, ackermann(m, n - 1)); + } +} + +__attribute__((export_name("run"))) int +run(int m, int n) +{ + int result = ackermann(m, n); + printf("ackermann(%d, %d)=%d\n", m, n, result); + return result; +} + +int +main() +{ + unsigned long m, n, result; + + // Example usage: + m = 3; + n = 2; + result = ackermann(m, n); + printf("Ackermann(%lu, %lu) = %lu\n", m, n, result); + + return 0; +} diff --git a/samples/linux-perf/wasm/fib.c b/samples/linux-perf/wasm/fib.c new file mode 100644 index 0000000000..cb928f65d0 --- /dev/null +++ b/samples/linux-perf/wasm/fib.c @@ -0,0 +1,32 @@ +#include +#include + +int +fibonacci(int n) +{ + if (n <= 0) + return 0; + + if (n == 1) + return 1; + + return fibonacci(n - 1) + fibonacci(n - 2); +} + +__attribute__((export_name("run"))) int +run(int n) +{ + int result = fibonacci(n); + printf("fibonacci(%d)=%d\n", n, result); + return result; +} + +int +main(int argc, char **argv) +{ + int n = atoi(argv[1]); + + printf("fibonacci(%d)=%d\n", n, fibonacci(n)); + + return 0; +} diff --git a/test-tools/flame-graph-helper/.gitignore b/test-tools/flame-graph-helper/.gitignore new file mode 100644 index 0000000000..b5ddc69ff8 --- /dev/null +++ b/test-tools/flame-graph-helper/.gitignore @@ -0,0 +1,2 @@ +*.* +!*.py \ No newline at end of file diff --git a/test-tools/flame-graph-helper/process_folded_data.py b/test-tools/flame-graph-helper/process_folded_data.py new file mode 100644 index 0000000000..e4650fe254 --- /dev/null +++ b/test-tools/flame-graph-helper/process_folded_data.py @@ -0,0 +1,325 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 Intel Corporation. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +""" +It is used to process *out.folded* file generated by [FlameGraph](https://github.com/brendangregg/FlameGraph). + +- translate jitted function names, which are in a form like `aot_func#N` or `[module name]#aot_func#N`, into corresponding names in a name section in .wasm +- divide the translated functions into different modules if the module name is specified in the symbol + +Usage: + +After +``` bash +# collect profiling data in perf.data + +$ perf script -i perf.data > out.perf + +$ ./FlameGraph/stackcollapse-perf.pl out.perf > out.folded +``` + +Use this script to translate the function names in out.folded + +``` +$ python translate_wasm_function_name.py --wabt_home --folded out.folded <.wasm> +# out.folded -> out.folded.translated +``` + +""" + +import argparse +import os +from pathlib import Path +import re +import shlex +import subprocess +from typing import Dict, List + + +# parse arguments like "foo=bar,fiz=biz" into a dictatory {foo:bar,fiz=biz} +class ParseKVArgs(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, dict()) + for value in values.split(","): + k, v = value.split("=") + getattr(namespace, self.dest)[k] = v + + +def calculate_import_function_count( + wasm_objdump_bin: Path, module_names: Dict[str, Path] +) -> Dict[str, int]: + """ + for every wasm file in , calculate the number of functions in the import section. + + using " -j Import -x " + """ + + assert wasm_objdump_bin.exists() + + import_function_counts = {} + for module_name, wasm_path in module_names.items(): + assert wasm_path.exists() + command = f"{wasm_objdump_bin} -j Import -x {wasm_path}" + p = subprocess.run( + shlex.split(command), + capture_output=True, + check=False, + text=True, + universal_newlines=True, + ) + + if p.stderr: + print("No content in import section") + import_function_counts[module_name] = 0 + continue + + import_function_count = 0 + for line in p.stdout.split(os.linesep): + line = line.strip() + + if not line: + continue + + if not " func" in line: + continue + + m = re.search(r"^-\s+func", line) + assert m + + import_function_count += 1 + + # print(f"! there are {import_function_count} import function in {module_name}") + import_function_counts[module_name] = import_function_count + + return import_function_counts + + +def collect_name_section_content( + wasm_objdump_bin: Path, module_names: Dict[str, Path] +) -> Dict[str, Dict[int, str]]: + """ + for every wasm file in , get the content of name section. + + execute "wasm_objdump_bin -j name -x wasm_file" + """ + assert wasm_objdump_bin.exists() + + name_sections = {} + for module_name, wasm_path in module_names.items(): + assert wasm_path.exists() + command = f"{wasm_objdump_bin} -j name -x {wasm_path}" + p = subprocess.run( + shlex.split(command), + capture_output=True, + check=False, + text=True, + universal_newlines=True, + ) + + if p.stderr: + print("No content in name section") + name_sections[module_name] = {} + continue + + name_section = {} + for line in p.stdout.split(os.linesep): + line = line.strip() + + if not line: + continue + + if not " func" in line: + continue + + # - func[N] <__imported_wasi_snapshot_preview1_fd_close> + m = re.match(r"- func\[(\d+)\] <(.+)>", line) + assert m + + func_index, func_name = m.groups() + name_section.update({int(func_index): func_name}) + + name_sections[module_name] = name_section + + return name_sections + + +def is_stack_check_mode(folded: Path) -> bool: + """ + check if there is a function name looks like "aot_func_internal#N", it means that WAMR adds a stack check function before the original function. + """ + with open(folded, "rt", encoding="utf-8") as f: + for line in f: + line = line.strip() + if "aot_func_internal" in line: + return True + return False + + +def replace_function_name( + import_function_counts: Dict[str, int], + name_sections: Dict[str, Dict[int, str]], + folded_in: Path, + module_names: Dict[str, Path], +) -> None: + """ + read content in . every line contains symbols which are separated by ";". + + Usually, all jitted functions are in the form of "aot_func#N". N is its function index. Use the index to find the corresponding function name in the name section. + + if there is a function name looks like "aot_func_internal#N", it means that WAMR adds a stack check function before the original function. + In this case, "aot_func#N" should be translated with "_precheck" as a suffix and "aot_func_internal#N" should be treated as the original one + """ + + assert folded_in.exists(), f"{folded_in} doesn't exist" + + stack_check_mode = is_stack_check_mode(folded_in) + + # every wasm has a translated out.folded, like out..folded.translated + folded_out_files = {} + for module_name in module_names.keys(): + wasm_folded_out_path = folded_in.with_suffix(f".{module_name}.translated") + print(f"-> write into {wasm_folded_out_path}") + folded_out_files[module_name] = wasm_folded_out_path.open( + "wt", encoding="utf-8" + ) + # Plus a default translated out.folded + default_folded_out_path = folded_in.with_suffix(".translated") + print(f"-> write into {default_folded_out_path}") + default_folded_out = default_folded_out_path.open("wt", encoding="utf-8") + + with folded_in.open("rt", encoding="utf-8") as f_in: + for line in f_in: + line = line.strip() + + m = re.match(r"(.*) (\d+)", line) + assert m + syms, samples = m.groups() + + new_line = [] + last_function_module_name = "" + for sym in syms.split(";"): + if not "aot_func" in sym: + new_line.append(sym) + continue + + # [module_name]#aot_func#N or aot_func#N + splitted = sym.split("#") + module_name = "" if splitted[0] == "aot_func" else splitted[0] + # remove [ and ] + module_name = module_name[1:-1] + + if len(module_name) == 0 and len(module_names) > 1: + raise RuntimeError( + f"❌ {sym} doesn't have a module name, but there are multiple wasm files" + ) + + if not module_name in module_names: + raise RuntimeError( + f"❌ can't find corresponds wasm file for {module_name}" + ) + + last_function_module_name = module_name + + func_idx = int(splitted[-1]) + # adjust index + func_idx = func_idx + import_function_counts[module_name] + + # print(f"🔍 {module_name} {splitted[1]} {func_idx}") + + if func_idx in name_sections[module_name]: + if len(module_name) > 0: + wasm_func_name = f"[Wasm] [{module_name}] {name_sections[module_name][func_idx]}" + else: + wasm_func_name = ( + f"[Wasm] {name_sections[module_name][func_idx]}" + ) + else: + if len(module_name) > 0: + wasm_func_name = f"[Wasm] [{module_name}] func[{func_idx}]" + else: + wasm_func_name = f"[Wasm] func[{func_idx}]" + + if stack_check_mode: + # aot_func_internal -> xxx + # aot_func --> xxx_precheck + if "aot_func" == splitted[1]: + wasm_func_name += "_precheck" + + new_line.append(wasm_func_name) + + line = ";".join(new_line) + line += f" {samples}" + + # always write into the default output + default_folded_out.write(line + os.linesep) + # based on the module name of last function, write into the corresponding output + if len(last_function_module_name) > 0: + folded_out_files[last_function_module_name].write(line + os.linesep) + + default_folded_out.close() + for f in folded_out_files.values(): + f.close() + + +def main(wabt_home: str, folded: str, module_names: Dict[str, Path]) -> None: + wabt_home = Path(wabt_home) + assert wabt_home.exists() + + folded = Path(folded) + assert folded.exists() + + wasm_objdump_bin = wabt_home.joinpath("bin", "wasm-objdump") + import_function_counts = calculate_import_function_count( + wasm_objdump_bin, module_names + ) + + name_sections = collect_name_section_content(wasm_objdump_bin, module_names) + + replace_function_name(import_function_counts, name_sections, folded, module_names) + + +if __name__ == "__main__": + argparse = argparse.ArgumentParser() + argparse.add_argument( + "--wabt_home", required=True, help="wabt home, like /opt/wabt-1.0.33" + ) + argparse.add_argument( + "--wasm", + action="append", + default=[], + help="wasm files for profiling before. like --wasm apple.wasm --wasm banana.wasm", + ) + argparse.add_argument( + "--wasm_names", + action=ParseKVArgs, + default={}, + metavar="module_name=wasm_file, ...", + help="multiple wasm files and their module names, like a=apple.wasm,b=banana.wasm,c=cake.wasm", + ) + argparse.add_argument( + "folded_file", + help="a out.folded generated by flamegraph/stackcollapse-perf.pl", + ) + + args = argparse.parse_args() + + if not args.wasm and not args.wasm_names: + print("Please specify wasm files with either --wasm or --wasm_names") + exit(1) + + # - only one wasm file. And there is no [module name] in out.folded + # - multiple wasm files. via `--wasm X --wasm Y --wasm Z`. And there is [module name] in out.folded. use the basename of wasm as the module name + # - multiple wasm files. via `--wasm_names X=x,Y=y,Z=z`. And there is [module name] in out.folded. use the specified module name + module_names = {} + if args.wasm_names: + for name, wasm_path in args.wasm_names.items(): + module_names[name] = Path(wasm_path) + else: + # use the basename of wasm as the module name + for wasm in args.wasm: + wasm_path = Path(wasm) + module_names[wasm_path.stem] = wasm_path + + main(args.wabt_home, args.folded_file, module_names) diff --git a/test-tools/trans-jitted-func-name/trans_wasm_func_name.py b/test-tools/trans-jitted-func-name/trans_wasm_func_name.py deleted file mode 100644 index 0206fc287b..0000000000 --- a/test-tools/trans-jitted-func-name/trans_wasm_func_name.py +++ /dev/null @@ -1,213 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2019 Intel Corporation. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -# -""" -It is used to translate jitted functions' names(in out.folded) to coorespond name in name section in .wasm - -Usage: - -After -``` -$ perf script -i perf.data > out.perf - -# fold call stacks -$ ./FlameGraph/stackcollapse-perf.pl out.perf > out.folded -``` - -Add a step: -``` -# translate jitted functions' names -$ python translate_wasm_function_name.py --wabt_home --folded out.folded <.wasm> -# out.folded -> out.folded.translated -$ ls out.folded.translated -``` - -Then -``` -# generate flamegraph -$ ./FlameGraph/flamegraph.pl out.folded.translated > perf.wasm.svg -``` - -""" - -import argparse -import os -from pathlib import Path -import re -import shlex -import subprocess - - -def preflight_check(wabt_home: Path) -> Path: - """ - if wasm-objdump exists in wabt_home - """ - wasm_objdump_bin = wabt_home.joinpath("bin", "wasm-objdump") - if not wasm_objdump_bin.exists(): - raise RuntimeError(f"wasm-objdump not found in {wabt_home}") - - return wasm_objdump_bin - - -def collect_import_section_content(wasm_objdump_bin: Path, wasm_file: Path) -> dict: - """ - execute "wasm_objdump_bin -j Import -x " and return a dict like {function: X, global: Y, memory: Z, table: N} - """ - assert wasm_objdump_bin.exists() - assert wasm_file.exists() - - command = f"{wasm_objdump_bin} -j Import -x {wasm_file}" - p = subprocess.run( - shlex.split(command), - capture_output=True, - check=False, - text=True, - universal_newlines=True, - ) - - if p.stderr: - print("No content in import section") - return {} - - import_section = {} - for line in p.stdout.split(os.linesep): - line = line.strip() - - if not line: - continue - - if re.search(r"^-\s+func", line): - import_section.update(function=import_section.get("function", 0) + 1) - else: - pass - - assert len(import_section) > 0, "failed to retrive content of import section" - return import_section - - -def collect_name_section_content(wasm_objdump_bin: Path, wasm_file: Path) -> dict: - """ - execute "wasm_objdump_bin -j name -x wasm_file" and store the output in a dict - {1: xxxx, 2: yyyy, 3: zzzz} - """ - assert wasm_objdump_bin.exists() - assert wasm_file.exists() - - command = f"{wasm_objdump_bin} -j name -x {wasm_file}" - p = subprocess.run( - shlex.split(command), - capture_output=True, - check=False, - text=True, - universal_newlines=True, - ) - - if p.stderr: - raise RuntimeError(f"not found name section in {wasm_file}") - - name_section = {} - for line in p.stdout.split(os.linesep): - line = line.strip() - - if not line: - continue - - # - func[0] <__imported_wasi_snapshot_preview1_fd_close> - if line.startswith("- func"): - m = re.match(r"- func\[(\d+)\] <(.+)>", line) - assert m - - func_index, func_name = m.groups() - name_section.update({int(func_index): func_name}) - - assert name_section - return name_section - - -def replace_function_name( - import_section: dict, name_section: dict, folded_in: str, folded_out: str -) -> None: - """ - read content in . each line will be like: - - quiche::BalsaFrame::ProcessHeaders;non-virtual thunk to Envoy::Http::Http1::BalsaParser::MessageDone;Envoy::Http::Http1::ConnectionImpl::onMessageComplete;Envoy::Http::Http1::ConnectionImpl::onMessageCompleteImpl;Envoy::Http::Http1::ServerConnectionImpl::onMessageCompleteBase;Envoy::Http::ConnectionManagerImpl::ActiveStream::decodeHeaders;Envoy::Http::FilterManager::decodeHeaders;virtual thunk to Envoy::Extensions::Common::Wasm::Context::decodeHeaders;proxy_wasm::ContextBase::onRequestHeaders;proxy_wasm::wamr::Wamr::getModuleFunctionImpl;wasm_func_call;wasm_runtime_call_wasm;wasm_call_function;call_wasm_with_hw_bound_check;wasm_interp_call_wasm;llvm_jit_call_func_bytecode;wasm_runtime_invoke_native;push_args_end;aot_func_internal#3302;aot_func_internal#3308;asm_sysvec_apic_timer_interrupt;sysvec_apic_timer_interrupt;__sysvec_apic_timer_interrupt;hrtimer_interrupt;__hrtimer_run_queues;__remove_hrtimer;rb_next 1110899 - - symbol names are spearated by ";" - - if there is a symbol named like "aot_func#XXX" or "aot_func_internal#XXX", it will be replaced with the function name in name section by index - """ - folded_in = Path(folded_in) - assert folded_in.exists() - folded_out = Path(folded_out) - - import_function_count = import_section.get("function", 0) - with folded_in.open("rt", encoding="utf-8") as f_in, folded_out.open( - "wt", encoding="utf-8" - ) as f_out: - precheck_mode = False - for line in f_in: - line = line.strip() - if "aot_func_internal" in line: - precheck_mode = True - - f_in.seek(0) - for line in f_in: - new_line = [] - line = line.strip() - - m = re.match(r"(.*) (\d+)", line) - syms, samples = m.groups() - for sym in syms.split(";"): - m = re.match(r"aot_func(_internal)?#(\d+)", sym) - if not m: - new_line.append(sym) - continue - - func_idx = int(m.groups()[-1]) + import_function_count - if func_idx in name_section: - wasm_func_name = f"[Wasm] {name_section[func_idx]}" - else: - wasm_func_name = ( - f"[Wasm] function[{func_idx + import_function_count}]" - ) - - if precheck_mode: - # aot_func_internal -> xxx - # aot_func --> xxx_precheck - wasm_func_name += "_precheck" if not m.groups()[0] else "" - else: - # aot_func --> xxx - pass - - new_line.append(wasm_func_name) - - line = ";".join(new_line) - line += f" {samples}" - f_out.write(line + os.linesep) - - print(f"⚙️ {folded_in} -> {folded_out}") - - -def main(wabt_home: str, wasm_file: str, folded: str) -> None: - wabt_home = Path(wabt_home) - wasm_file = Path(wasm_file) - - wasm_objdump_bin = preflight_check(wabt_home) - import_section = collect_import_section_content(wasm_objdump_bin, wasm_file) - name_section = collect_name_section_content(wasm_objdump_bin, wasm_file) - - replace_function_name(import_section, name_section, folded, folded + ".translated") - - -if __name__ == "__main__": - argparse = argparse.ArgumentParser() - argparse.add_argument( - "--folded", help="stackcollapse-perf.pl generated, like out.folded" - ) - argparse.add_argument("wasm_file", help="wasm file") - argparse.add_argument("--wabt_home", help="wabt home, like /opt/wabt-1.0.33") - - args = argparse.parse_args() - main(args.wabt_home, args.wasm_file, args.folded)