diff --git a/core/iwasm/common/wasm_exec_env.c b/core/iwasm/common/wasm_exec_env.c index 47752950f2..b3e18b7918 100644 --- a/core/iwasm/common/wasm_exec_env.c +++ b/core/iwasm/common/wasm_exec_env.c @@ -87,6 +87,11 @@ wasm_exec_env_create_internal(struct WASMModuleInstanceCommon *module_inst, #if WASM_ENABLE_INSTRUCTION_METERING != 0 exec_env->instructions_to_execute = -1; + exec_env->metering_suspended = false; + exec_env->metering_suspend_frame = NULL; + exec_env->metering_suspend_function = NULL; + exec_env->metering_suspend_argc = 0; + exec_env->metering_suspend_argv = NULL; #endif return exec_env; diff --git a/core/iwasm/common/wasm_exec_env.h b/core/iwasm/common/wasm_exec_env.h index 5d80312fb1..98d380aa58 100644 --- a/core/iwasm/common/wasm_exec_env.h +++ b/core/iwasm/common/wasm_exec_env.h @@ -90,6 +90,21 @@ typedef struct WASMExecEnv { #if WASM_ENABLE_INSTRUCTION_METERING != 0 /* instructions to execute */ int instructions_to_execute; + + /* true when classic interpreter suspended by instruction metering */ + bool metering_suspended; + + /* top frame to resume from when metering_suspended is true */ + struct WASMInterpFrame *metering_suspend_frame; + + /* function associated with metering suspended frame */ + struct WASMFunctionInstance *metering_suspend_function; + + /* argc captured for metering suspended function */ + uint32 metering_suspend_argc; + + /* argv captured for metering suspended function */ + uint32 *metering_suspend_argv; #endif #if WASM_ENABLE_FAST_JIT != 0 diff --git a/core/iwasm/common/wasm_runtime_common.c b/core/iwasm/common/wasm_runtime_common.c index 28b957798e..4b879e10d6 100644 --- a/core/iwasm/common/wasm_runtime_common.c +++ b/core/iwasm/common/wasm_runtime_common.c @@ -2469,6 +2469,30 @@ wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env, { exec_env->instructions_to_execute = instructions_to_execute; } + +bool +wasm_runtime_resume_wasm(WASMExecEnv *exec_env) +{ + WASMFunctionInstanceCommon *function; + + if (!wasm_runtime_exec_env_check(exec_env)) { + LOG_ERROR("Invalid exec env stack info."); + return false; + } + + if (!exec_env->metering_suspended || !exec_env->metering_suspend_function + || !exec_env->metering_suspend_argv) { + wasm_runtime_set_exception(exec_env->module_inst, + "no metering resume is pending"); + return false; + } + + function = + (WASMFunctionInstanceCommon *)exec_env->metering_suspend_function; + return wasm_runtime_call_wasm(exec_env, function, + exec_env->metering_suspend_argc, + exec_env->metering_suspend_argv); +} #endif WASMFuncType * diff --git a/core/iwasm/common/wasm_runtime_common.h b/core/iwasm/common/wasm_runtime_common.h index 0477387ed0..9deb3ddae5 100644 --- a/core/iwasm/common/wasm_runtime_common.h +++ b/core/iwasm/common/wasm_runtime_common.h @@ -877,6 +877,10 @@ wasm_runtime_set_native_stack_boundary(WASMExecEnv *exec_env, WASM_RUNTIME_API_EXTERN void wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env, int instructions_to_execute); + +/* See wasm_export.h for description */ +WASM_RUNTIME_API_EXTERN bool +wasm_runtime_resume_wasm(WASMExecEnv *exec_env); #endif #if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0 diff --git a/core/iwasm/include/wasm_export.h b/core/iwasm/include/wasm_export.h index 86f7c22b17..7b739da714 100644 --- a/core/iwasm/include/wasm_export.h +++ b/core/iwasm/include/wasm_export.h @@ -1928,6 +1928,26 @@ WASM_RUNTIME_API_EXTERN void wasm_runtime_set_instruction_count_limit(wasm_exec_env_t exec_env, int instruction_count); +/** + * Resume wasm execution after an instruction metering trap. + * + * When instruction metering is enabled and execution stops with + * `instruction limit exceeded`, the runtime may preserve interpreter frame + * state in the exec env. This API resumes from that preserved state without + * requiring the host to call a specific exported function again. + * + * The caller should set a new instruction budget with + * `wasm_runtime_set_instruction_count_limit(...)` before resuming. + * + * @param exec_env the execution environment + * + * @return true if resumed execution succeeds, false otherwise and exception + * will be thrown, the caller can call wasm_runtime_get_exception to get + * exception info. + */ +WASM_RUNTIME_API_EXTERN bool +wasm_runtime_resume_wasm(wasm_exec_env_t exec_env); + /** * Dump runtime memory consumption, including: * Exec env memory consumption diff --git a/core/iwasm/interpreter/wasm_interp_classic.c b/core/iwasm/interpreter/wasm_interp_classic.c index abde189ebc..7351838f0d 100644 --- a/core/iwasm/interpreter/wasm_interp_classic.c +++ b/core/iwasm/interpreter/wasm_interp_classic.c @@ -1558,6 +1558,37 @@ get_global_addr(uint8 *global_data, WASMGlobalInstance *global) #define CHECK_INSTRUCTION_LIMIT() (void)0 #endif +#if WASM_ENABLE_INSTRUCTION_METERING != 0 +static inline bool +is_instruction_metering_exception(WASMModuleInstance *module_inst) +{ + const char *exception = wasm_get_exception(module_inst); + return exception && strstr(exception, "instruction limit exceeded"); +} + +static inline void +clear_metering_suspend_state(WASMExecEnv *exec_env) +{ + exec_env->metering_suspended = false; + exec_env->metering_suspend_frame = NULL; + exec_env->metering_suspend_function = NULL; + exec_env->metering_suspend_argc = 0; + exec_env->metering_suspend_argv = NULL; +} + +static inline WASMRuntimeFrame * +find_metering_resume_call_boundary(WASMRuntimeFrame *suspended_frame) +{ + WASMRuntimeFrame *frame = suspended_frame; + + while (frame && frame->prev_frame && frame->prev_frame->function) { + frame = frame->prev_frame; + } + + return frame ? frame->prev_frame : NULL; +} +#endif + static void wasm_interp_call_func_bytecode(WASMModuleInstance *module, WASMExecEnv *exec_env, @@ -1671,6 +1702,16 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, #undef HANDLE_OPCODE #endif +#if WASM_ENABLE_INSTRUCTION_METERING != 0 + if (prev_frame && prev_frame->function == cur_func && prev_frame->ip) { + RECOVER_CONTEXT(prev_frame); +#if WASM_ENABLE_TAIL_CALL != 0 || WASM_ENABLE_GC != 0 + is_return_call = false; +#endif + goto resume_func; + } +#endif + #if WASM_ENABLE_LABELS_AS_VALUES == 0 while (frame_ip < frame_ip_end) { opcode = *frame_ip++; @@ -6857,6 +6898,9 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, wasm_exec_env_set_cur_frame(exec_env, frame); } +#if WASM_ENABLE_INSTRUCTION_METERING != 0 + resume_func: +#endif #if WASM_ENABLE_THREAD_MGR != 0 CHECK_SUSPEND_FLAGS(); #endif @@ -7413,6 +7457,10 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env, wasm_runtime_get_running_mode((WASMModuleInstanceCommon *)module_inst); /* Allocate sufficient cells for all kinds of return values. */ bool alloc_frame = true; +#if WASM_ENABLE_INSTRUCTION_METERING != 0 + bool resume_metering = false; + WASMRuntimeFrame *suspended_frame = NULL; +#endif if (argc < function->param_cell_num) { char buf[128]; @@ -7456,7 +7504,34 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env, #endif } - if (alloc_frame) { +#if WASM_ENABLE_INSTRUCTION_METERING != 0 + if (running_mode == Mode_Interp && exec_env->metering_suspended) { + suspended_frame = exec_env->metering_suspend_frame; + if (!suspended_frame || suspended_frame->function != function) { + wasm_set_exception(module_inst, + "cannot call different function while metering " + "resume is pending"); + return; + } + frame = find_metering_resume_call_boundary(suspended_frame); + if (!frame) { + wasm_set_exception(module_inst, + "invalid metering resume frame state"); + clear_metering_suspend_state(exec_env); + return; + } + + resume_metering = true; + prev_frame = frame->prev_frame; + wasm_exec_env_set_cur_frame(exec_env, suspended_frame); + } +#endif + + if (alloc_frame +#if WASM_ENABLE_INSTRUCTION_METERING != 0 + && !resume_metering +#endif + ) { unsigned all_cell_num = function->ret_cell_num > 2 ? function->ret_cell_num : 2; unsigned frame_size; @@ -7513,7 +7588,13 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env, else { if (running_mode == Mode_Interp) { wasm_interp_call_func_bytecode(module_inst, exec_env, function, - frame); +#if WASM_ENABLE_INSTRUCTION_METERING != 0 + resume_metering ? suspended_frame + : frame +#else + frame +#endif + ); } #if WASM_ENABLE_FAST_JIT != 0 else if (running_mode == Mode_Fast_JIT) { @@ -7554,6 +7635,27 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env, #endif } +#if WASM_ENABLE_INSTRUCTION_METERING != 0 + if ((running_mode == Mode_Interp) + && is_instruction_metering_exception(module_inst)) { + exec_env->metering_suspended = true; + exec_env->metering_suspend_frame = + wasm_exec_env_get_cur_frame(exec_env); + if (exec_env->metering_suspend_frame) { + exec_env->metering_suspend_function = + exec_env->metering_suspend_frame->function; + exec_env->metering_suspend_argc = argc; + exec_env->metering_suspend_argv = argv; + } + else { + exec_env->metering_suspend_function = NULL; + exec_env->metering_suspend_argc = 0; + exec_env->metering_suspend_argv = NULL; + } + return; + } +#endif + /* Output the return value to the caller */ if (!wasm_copy_exception(module_inst, NULL)) { if (alloc_frame) { @@ -7575,4 +7677,8 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env, wasm_exec_env_set_cur_frame(exec_env, prev_frame); FREE_FRAME(exec_env, frame); } + +#if WASM_ENABLE_INSTRUCTION_METERING != 0 + clear_metering_suspend_state(exec_env); +#endif } diff --git a/core/iwasm/interpreter/wasm_interp_fast.c b/core/iwasm/interpreter/wasm_interp_fast.c index 1368cf0387..90c22bb5ce 100644 --- a/core/iwasm/interpreter/wasm_interp_fast.c +++ b/core/iwasm/interpreter/wasm_interp_fast.c @@ -90,8 +90,21 @@ typedef float64 CellType_F64; } while (0) #if WASM_ENABLE_INSTRUCTION_METERING != 0 +#if WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS != 0 +#define ROLLBACK_IP_AFTER_METERING_CHECK() \ + do { \ + frame_ip -= sizeof(void *); \ + } while (0) +#else +#define ROLLBACK_IP_AFTER_METERING_CHECK() \ + do { \ + frame_ip -= sizeof(int32); \ + } while (0) +#endif + #define CHECK_INSTRUCTION_LIMIT() \ if (instructions_left == 0) { \ + ROLLBACK_IP_AFTER_METERING_CHECK(); \ wasm_set_exception(module, "instruction limit exceeded"); \ goto got_exception; \ } \ @@ -102,6 +115,37 @@ typedef float64 CellType_F64; #define CHECK_INSTRUCTION_LIMIT() (void)0 #endif +#if WASM_ENABLE_INSTRUCTION_METERING != 0 +static inline bool +is_instruction_metering_exception(WASMModuleInstance *module_inst) +{ + const char *exception = wasm_get_exception(module_inst); + return exception && strstr(exception, "instruction limit exceeded"); +} + +static inline void +clear_metering_suspend_state(WASMExecEnv *exec_env) +{ + exec_env->metering_suspended = false; + exec_env->metering_suspend_frame = NULL; + exec_env->metering_suspend_function = NULL; + exec_env->metering_suspend_argc = 0; + exec_env->metering_suspend_argv = NULL; +} + +static inline WASMRuntimeFrame * +find_metering_resume_call_boundary(WASMRuntimeFrame *suspended_frame) +{ + WASMRuntimeFrame *frame = suspended_frame; + + while (frame && frame->prev_frame && frame->prev_frame->function) { + frame = frame->prev_frame; + } + + return frame ? frame->prev_frame : NULL; +} +#endif + static inline uint32 rotl32(uint32 n, uint32 c) { @@ -1594,6 +1638,16 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, } #endif +#if WASM_ENABLE_INSTRUCTION_METERING != 0 + if (prev_frame && prev_frame->function == cur_func && prev_frame->ip) { + RECOVER_CONTEXT(prev_frame); +#if WASM_ENABLE_TAIL_CALL != 0 || WASM_ENABLE_GC != 0 + is_return_call = false; +#endif + goto resume_func; + } +#endif + #if WASM_ENABLE_LABELS_AS_VALUES == 0 while (frame_ip < frame_ip_end) { opcode = *frame_ip++; @@ -7788,6 +7842,14 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, HANDLE_OP_END(); } +#if WASM_ENABLE_INSTRUCTION_METERING != 0 + resume_func: +#if WASM_ENABLE_THREAD_MGR != 0 + CHECK_SUSPEND_FLAGS(); +#endif + HANDLE_OP_END(); +#endif + return_func: { FREE_FRAME(exec_env, frame); @@ -7888,13 +7950,17 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env, WASMFunctionInstance *function, uint32 argc, uint32 argv[]) { - WASMRuntimeFrame *prev_frame = wasm_exec_env_get_cur_frame(exec_env); - WASMInterpFrame *frame, *outs_area; + WASMRuntimeFrame *frame = NULL, *prev_frame, *outs_area; /* Allocate sufficient cells for all kinds of return values. */ unsigned all_cell_num = function->ret_cell_num > 2 ? function->ret_cell_num : 2, i; + bool alloc_frame = true; +#if WASM_ENABLE_INSTRUCTION_METERING != 0 + bool resume_metering = false; + WASMRuntimeFrame *suspended_frame = NULL; +#endif /* This frame won't be used by JITed code, so only allocate interp frame here. */ unsigned frame_size; @@ -7927,33 +7993,66 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env, } #endif - if (!(frame = - ALLOC_FRAME(exec_env, frame_size, (WASMInterpFrame *)prev_frame))) - return; + prev_frame = wasm_exec_env_get_cur_frame(exec_env); - outs_area = wasm_exec_env_wasm_stack_top(exec_env); - frame->function = NULL; - frame->ip = NULL; - /* There is no local variable. */ - frame->lp = frame->operand + 0; +#if WASM_ENABLE_INSTRUCTION_METERING != 0 + if (exec_env->metering_suspended) { + suspended_frame = exec_env->metering_suspend_frame; + if (!suspended_frame || suspended_frame->function != function) { + wasm_set_exception(module_inst, + "cannot call different function while metering " + "resume is pending"); + return; + } + frame = find_metering_resume_call_boundary(suspended_frame); + if (!frame) { + wasm_set_exception(module_inst, + "invalid metering resume frame state"); + clear_metering_suspend_state(exec_env); + return; + } + + resume_metering = true; + prev_frame = frame->prev_frame; + wasm_exec_env_set_cur_frame(exec_env, suspended_frame); + } +#endif + + if (alloc_frame +#if WASM_ENABLE_INSTRUCTION_METERING != 0 + && !resume_metering +#endif + ) { + if (!(frame = ALLOC_FRAME(exec_env, frame_size, + (WASMInterpFrame *)prev_frame))) + return; + + outs_area = wasm_exec_env_wasm_stack_top(exec_env); + frame->function = NULL; + frame->ip = NULL; + /* There is no local variable. */ + frame->lp = frame->operand + 0; #if WASM_ENABLE_GC != 0 - frame->frame_ref = - (uint8 *)(frame->lp - + (function->ret_cell_num > 2 ? function->ret_cell_num : 2)); + frame->frame_ref = + (uint8 *)(frame->lp + + (function->ret_cell_num > 2 ? function->ret_cell_num + : 2)); #endif - frame->ret_offset = 0; + frame->ret_offset = 0; - if ((uint8 *)(outs_area->operand + function->const_cell_num + argc) - > exec_env->wasm_stack.top_boundary) { - wasm_set_exception((WASMModuleInstance *)exec_env->module_inst, - "wasm operand stack overflow"); - return; - } + if ((uint8 *)(outs_area->operand + function->const_cell_num + argc) + > exec_env->wasm_stack.top_boundary) { + wasm_set_exception((WASMModuleInstance *)exec_env->module_inst, + "wasm operand stack overflow"); + return; + } - if (argc > 0) - word_copy(outs_area->operand + function->const_cell_num, argv, argc); + if (argc > 0) + word_copy(outs_area->operand + function->const_cell_num, argv, + argc); - wasm_exec_env_set_cur_frame(exec_env, frame); + wasm_exec_env_set_cur_frame(exec_env, frame); + } #if defined(os_writegsbase) { @@ -7980,8 +8079,34 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env, } } else { - wasm_interp_call_func_bytecode(module_inst, exec_env, function, frame); + wasm_interp_call_func_bytecode(module_inst, exec_env, function, +#if WASM_ENABLE_INSTRUCTION_METERING != 0 + resume_metering ? suspended_frame : frame +#else + frame +#endif + ); + } + +#if WASM_ENABLE_INSTRUCTION_METERING != 0 + if (is_instruction_metering_exception(module_inst)) { + exec_env->metering_suspended = true; + exec_env->metering_suspend_frame = + wasm_exec_env_get_cur_frame(exec_env); + if (exec_env->metering_suspend_frame) { + exec_env->metering_suspend_function = + exec_env->metering_suspend_frame->function; + exec_env->metering_suspend_argc = argc; + exec_env->metering_suspend_argv = argv; + } + else { + exec_env->metering_suspend_function = NULL; + exec_env->metering_suspend_argc = 0; + exec_env->metering_suspend_argv = NULL; + } + return; } +#endif /* Output the return value to the caller */ if (!wasm_copy_exception(module_inst, NULL)) { @@ -7996,8 +8121,14 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env, #endif } - wasm_exec_env_set_cur_frame(exec_env, prev_frame); - FREE_FRAME(exec_env, frame); + if (alloc_frame) { + wasm_exec_env_set_cur_frame(exec_env, prev_frame); + FREE_FRAME(exec_env, frame); + } + +#if WASM_ENABLE_INSTRUCTION_METERING != 0 + clear_metering_suspend_state(exec_env); +#endif #if WASM_ENABLE_OPCODE_COUNTER != 0 wasm_interp_dump_op_count(); #endif diff --git a/doc/build_wamr.md b/doc/build_wamr.md index 4b7fbfd9f0..ea239ff7a3 100644 --- a/doc/build_wamr.md +++ b/doc/build_wamr.md @@ -612,6 +612,12 @@ SIMDE (SIMD Everywhere) implements SIMD operations in fast interpreter mode. > [!NOTE] > This limits the number of instructions a wasm module instance can run. Call `wasm_runtime_set_instruction_count_limit(...)` before `wasm_runtime_call_*(...)` to enforce the cap. +> +> In classic and fast interpreter modes, when instruction budget is exhausted, +> the runtime raises `instruction limit exceeded`. If the host sets a new budget +> and calls the same function again (optionally after `wasm_runtime_clear_exception(...)`), +> execution resumes from preserved interpreter state instead of restarting from +> function entry. > [!WARNING] > This is only supported in classic interpreter mode. diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index ceb1c0b6ea..781cf85d11 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -90,6 +90,7 @@ add_subdirectory(tid-allocator) add_subdirectory(unsupported-features) add_subdirectory(exception-handling) add_subdirectory(running-modes) +add_subdirectory(instruction-metering) if(FULL_TEST) message(STATUS "FULL_TEST=ON: include llm-enhanced-test") diff --git a/tests/unit/instruction-metering/CMakeLists.txt b/tests/unit/instruction-metering/CMakeLists.txt new file mode 100644 index 0000000000..134d8c3144 --- /dev/null +++ b/tests/unit/instruction-metering/CMakeLists.txt @@ -0,0 +1,54 @@ +cmake_minimum_required(VERSION 3.14) + +project(test-instruction-metering) + +add_definitions(-DRUN_ON_LINUX) + +set(WAMR_BUILD_AOT 0) +set(WAMR_BUILD_FAST_INTERP 1) +set(WAMR_BUILD_INTERP 1) +set(WAMR_BUILD_JIT 0) +set(WAMR_BUILD_LIBC_WASI 0) +set(WAMR_BUILD_APP_FRAMEWORK 0) +set(WAMR_BUILD_INSTRUCTION_METERING 1) + +if(NOT DEFINED WASI_SDK_DIR) + set(WASI_SDK_DIR "/opt/wasi-sdk") +endif() +set(WASISDK_TOOLCHAIN "${WASI_SDK_DIR}/share/cmake/wasi-sdk.cmake") + +include(ExternalProject) +include(GoogleTest) +if (TARGET gtest) + get_target_property(GTEST_INCLUDE_DIRS gtest INTERFACE_INCLUDE_DIRECTORIES) + if (GTEST_INCLUDE_DIRS) + include_directories(${GTEST_INCLUDE_DIRS}) + endif() +endif() +ExternalProject_Add( + instruction_metering_wasm_apps + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/wasm-apps + BUILD_ALWAYS YES + CONFIGURE_COMMAND ${CMAKE_COMMAND} -S ${CMAKE_CURRENT_SOURCE_DIR}/wasm-apps -B build + -DWASI_SDK_PREFIX=${WASI_SDK_DIR} + -DCMAKE_TOOLCHAIN_FILE=${WASISDK_TOOLCHAIN} + BUILD_COMMAND ${CMAKE_COMMAND} --build build + INSTALL_COMMAND ${CMAKE_COMMAND} --install build --prefix ${CMAKE_CURRENT_BINARY_DIR} +) + +include(../unit_common.cmake) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +file(GLOB_RECURSE source_all ${CMAKE_CURRENT_SOURCE_DIR}/*.cc) + +set(unit_test_sources + ${source_all} + ${WAMR_RUNTIME_LIB_SOURCE} + ${UNCOMMON_SHARED_SOURCE} +) + +add_executable(instruction_metering_test ${unit_test_sources}) +target_link_libraries(instruction_metering_test gtest_main gtest) +add_dependencies(instruction_metering_test instruction_metering_wasm_apps) + +gtest_discover_tests(instruction_metering_test) diff --git a/tests/unit/instruction-metering/instruction_metering_resume_test.cc b/tests/unit/instruction-metering/instruction_metering_resume_test.cc new file mode 100644 index 0000000000..207e3eb8c9 --- /dev/null +++ b/tests/unit/instruction-metering/instruction_metering_resume_test.cc @@ -0,0 +1,224 @@ +#include "gtest/gtest.h" +#include "test_helper.h" + +#include + +#include "bh_read_file.h" +#include "wasm_export.h" + +class instruction_metering_resume_test_suite : public testing::Test +{ + protected: + WAMRRuntimeRAII<512 * 1024> runtime; +}; + +static std::vector +load_wasm_file(const std::string &path) +{ + unsigned size = 0; + uint8_t *buf = (uint8_t *)bh_read_file_to_buffer(path.c_str(), &size); + EXPECT_NE(buf, nullptr); + std::vector out(buf, buf + size); + wasm_runtime_free(buf); + return out; +} + +TEST_F(instruction_metering_resume_test_suite, + resume_after_instruction_limit_continues_execution) +{ + std::string wasm_path = get_test_binary_dir() + "/resume_counter.wasm"; + auto wasm = load_wasm_file(wasm_path); + + char error_buf[128] = { 0 }; + wasm_module_t module = wasm_runtime_load(wasm.data(), (uint32_t)wasm.size(), + error_buf, sizeof(error_buf)); + ASSERT_NE(module, nullptr) << error_buf; + + wasm_module_inst_t inst = wasm_runtime_instantiate( + module, 16 * 1024, 16 * 1024, error_buf, sizeof(error_buf)); + ASSERT_NE(inst, nullptr) << error_buf; + + wasm_exec_env_t exec_env = wasm_runtime_create_exec_env(inst, 16 * 1024); + ASSERT_NE(exec_env, nullptr); + + wasm_function_inst_t fn = wasm_runtime_lookup_function(inst, "countdown"); + ASSERT_NE(fn, nullptr); + + uint32_t argv[1] = { 5000 }; + + wasm_runtime_set_instruction_count_limit(exec_env, 2); + bool ok = wasm_runtime_call_wasm(exec_env, fn, 1, argv); + EXPECT_FALSE(ok); + ASSERT_NE(wasm_runtime_get_exception(inst), nullptr); + EXPECT_NE(std::string(wasm_runtime_get_exception(inst)) + .find("instruction limit exceeded"), + std::string::npos); + + wasm_runtime_clear_exception(inst); + wasm_runtime_set_instruction_count_limit(exec_env, 200000); + ok = wasm_runtime_call_wasm(exec_env, fn, 1, argv); + + EXPECT_TRUE(ok); + EXPECT_EQ(argv[0], 0u); + + wasm_runtime_destroy_exec_env(exec_env); + wasm_runtime_deinstantiate(inst); + wasm_runtime_unload(module); +} + +TEST_F(instruction_metering_resume_test_suite, + reject_different_function_while_resume_pending) +{ + std::string wasm_path = get_test_binary_dir() + "/resume_counter.wasm"; + auto wasm = load_wasm_file(wasm_path); + + char error_buf[128] = { 0 }; + wasm_module_t module = wasm_runtime_load(wasm.data(), (uint32_t)wasm.size(), + error_buf, sizeof(error_buf)); + ASSERT_NE(module, nullptr) << error_buf; + + wasm_module_inst_t inst = wasm_runtime_instantiate( + module, 16 * 1024, 16 * 1024, error_buf, sizeof(error_buf)); + ASSERT_NE(inst, nullptr) << error_buf; + + wasm_exec_env_t exec_env = wasm_runtime_create_exec_env(inst, 16 * 1024); + ASSERT_NE(exec_env, nullptr); + + wasm_function_inst_t countdown = + wasm_runtime_lookup_function(inst, "countdown"); + wasm_function_inst_t noop = wasm_runtime_lookup_function(inst, "noop"); + ASSERT_NE(countdown, nullptr); + ASSERT_NE(noop, nullptr); + + uint32_t argv_countdown[1] = { 1000 }; + wasm_runtime_set_instruction_count_limit(exec_env, 10); + bool ok = + wasm_runtime_call_wasm(exec_env, countdown, 1, argv_countdown); + EXPECT_FALSE(ok); + ASSERT_NE(wasm_runtime_get_exception(inst), nullptr); + EXPECT_NE(std::string(wasm_runtime_get_exception(inst)) + .find("instruction limit exceeded"), + std::string::npos); + + wasm_runtime_clear_exception(inst); + uint32_t argv_noop[1] = { 0 }; + ok = wasm_runtime_call_wasm(exec_env, noop, 0, argv_noop); + + EXPECT_FALSE(ok); + ASSERT_NE(wasm_runtime_get_exception(inst), nullptr); + EXPECT_NE( + std::string(wasm_runtime_get_exception(inst)) + .find("cannot call different function while metering resume is " + "pending"), + std::string::npos); + + wasm_runtime_destroy_exec_env(exec_env); + wasm_runtime_deinstantiate(inst); + wasm_runtime_unload(module); +} + +TEST_F(instruction_metering_resume_test_suite, + resume_nested_call_from_same_export_continues_execution) +{ + std::string wasm_path = get_test_binary_dir() + "/resume_nested.wasm"; + auto wasm = load_wasm_file(wasm_path); + + char error_buf[128] = { 0 }; + wasm_module_t module = wasm_runtime_load(wasm.data(), (uint32_t)wasm.size(), + error_buf, sizeof(error_buf)); + ASSERT_NE(module, nullptr) << error_buf; + + wasm_module_inst_t inst = wasm_runtime_instantiate( + module, 16 * 1024, 16 * 1024, error_buf, sizeof(error_buf)); + ASSERT_NE(inst, nullptr) << error_buf; + + wasm_exec_env_t exec_env = wasm_runtime_create_exec_env(inst, 16 * 1024); + ASSERT_NE(exec_env, nullptr); + + wasm_function_inst_t install = wasm_runtime_lookup_function(inst, "install"); + ASSERT_NE(install, nullptr); + + uint32_t argv[1] = { 5000 }; + + wasm_runtime_set_instruction_count_limit(exec_env, 2); + bool ok = wasm_runtime_call_wasm(exec_env, install, 1, argv); + EXPECT_FALSE(ok); + ASSERT_NE(wasm_runtime_get_exception(inst), nullptr); + EXPECT_NE(std::string(wasm_runtime_get_exception(inst)) + .find("instruction limit exceeded"), + std::string::npos); + + wasm_runtime_clear_exception(inst); + wasm_runtime_set_instruction_count_limit(exec_env, 200000); + ok = wasm_runtime_call_wasm(exec_env, install, 1, argv); + if (!ok) { + const char *ex = wasm_runtime_get_exception(inst); + if (ex) { + fprintf(stderr, "nested resume failure exception: %s\n", ex); + } + } + + EXPECT_TRUE(ok); + EXPECT_EQ(argv[0], 0u); + + wasm_runtime_destroy_exec_env(exec_env); + wasm_runtime_deinstantiate(inst); + wasm_runtime_unload(module); +} + +TEST_F(instruction_metering_resume_test_suite, + resume_api_continues_nested_execution_without_recalling_export) +{ + std::string wasm_path = get_test_binary_dir() + "/resume_nested.wasm"; + auto wasm = load_wasm_file(wasm_path); + + char error_buf[128] = { 0 }; + wasm_module_t module = wasm_runtime_load(wasm.data(), (uint32_t)wasm.size(), + error_buf, sizeof(error_buf)); + ASSERT_NE(module, nullptr) << error_buf; + + wasm_module_inst_t inst = wasm_runtime_instantiate( + module, 16 * 1024, 16 * 1024, error_buf, sizeof(error_buf)); + ASSERT_NE(inst, nullptr) << error_buf; + + wasm_exec_env_t exec_env = wasm_runtime_create_exec_env(inst, 16 * 1024); + ASSERT_NE(exec_env, nullptr); + + wasm_function_inst_t install = wasm_runtime_lookup_function(inst, "install"); + ASSERT_NE(install, nullptr); + + uint32_t argv[1] = { 5000 }; + + wasm_runtime_set_instruction_count_limit(exec_env, 20); + bool ok = wasm_runtime_call_wasm(exec_env, install, 1, argv); + EXPECT_FALSE(ok); + ASSERT_NE(wasm_runtime_get_exception(inst), nullptr); + EXPECT_NE(std::string(wasm_runtime_get_exception(inst)) + .find("instruction limit exceeded"), + std::string::npos); + + wasm_runtime_clear_exception(inst); + wasm_runtime_set_instruction_count_limit(exec_env, 200000); + ok = wasm_runtime_resume_wasm(exec_env); + if (!ok) { + const char *ex = wasm_runtime_get_exception(inst); + if (ex) { + fprintf(stderr, "resume api failure exception: %s\n", ex); + } + } + + EXPECT_TRUE(ok); + + if (ok) { + uint32_t verify_argv[1] = { 5000 }; + ok = wasm_runtime_call_wasm(exec_env, install, 1, verify_argv); + EXPECT_TRUE(ok); + if (ok) { + EXPECT_EQ(verify_argv[0], 0u); + } + } + + wasm_runtime_destroy_exec_env(exec_env); + wasm_runtime_deinstantiate(inst); + wasm_runtime_unload(module); +} diff --git a/tests/unit/instruction-metering/wasm-apps/CMakeLists.txt b/tests/unit/instruction-metering/wasm-apps/CMakeLists.txt new file mode 100644 index 0000000000..d8bf59b422 --- /dev/null +++ b/tests/unit/instruction-metering/wasm-apps/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.13) +project(instruction_metering_wasm_apps) + +add_executable(resume_counter resume_counter.c) +set_target_properties(resume_counter PROPERTIES SUFFIX .wasm) +target_link_options(resume_counter PRIVATE + "-Wl,--no-entry" + "-Wl,--export=countdown" + "-Wl,--export=noop") +install(TARGETS resume_counter DESTINATION .) + +add_executable(resume_nested resume_nested.c) +set_target_properties(resume_nested PROPERTIES SUFFIX .wasm) +target_link_options(resume_nested PRIVATE + "-Wl,--no-entry" + "-Wl,--export=install" + "-Wl,--export=noop") +install(TARGETS resume_nested DESTINATION .) diff --git a/tests/unit/instruction-metering/wasm-apps/resume_counter.c b/tests/unit/instruction-metering/wasm-apps/resume_counter.c new file mode 100644 index 0000000000..8f9f8c087f --- /dev/null +++ b/tests/unit/instruction-metering/wasm-apps/resume_counter.c @@ -0,0 +1,17 @@ +#include + +int32_t +countdown(int32_t n) +{ + while (n > 0) { + n = n - 1; + } + + return n; +} + +int32_t +noop(void) +{ + return 7; +} diff --git a/tests/unit/instruction-metering/wasm-apps/resume_nested.c b/tests/unit/instruction-metering/wasm-apps/resume_nested.c new file mode 100644 index 0000000000..35583f80ae --- /dev/null +++ b/tests/unit/instruction-metering/wasm-apps/resume_nested.c @@ -0,0 +1,23 @@ +#include + +static int32_t +helper_loop(int32_t n) +{ + while (n > 0) { + n = n - 1; + } + + return n; +} + +int32_t +install(int32_t n) +{ + return helper_loop(n); +} + +int32_t +noop(void) +{ + return 9; +}