This commit is contained in:
Alix ANNERAUD 2026-04-08 13:13:35 +02:00 committed by GitHub
commit c947c08c66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 677 additions and 29 deletions

View File

@ -87,6 +87,11 @@ wasm_exec_env_create_internal(struct WASMModuleInstanceCommon *module_inst,
#if WASM_ENABLE_INSTRUCTION_METERING != 0 #if WASM_ENABLE_INSTRUCTION_METERING != 0
exec_env->instructions_to_execute = -1; 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 #endif
return exec_env; return exec_env;

View File

@ -90,6 +90,21 @@ typedef struct WASMExecEnv {
#if WASM_ENABLE_INSTRUCTION_METERING != 0 #if WASM_ENABLE_INSTRUCTION_METERING != 0
/* instructions to execute */ /* instructions to execute */
int 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 #endif
#if WASM_ENABLE_FAST_JIT != 0 #if WASM_ENABLE_FAST_JIT != 0

View File

@ -2473,6 +2473,30 @@ wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env,
{ {
exec_env->instructions_to_execute = instructions_to_execute; 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 #endif
WASMFuncType * WASMFuncType *

View File

@ -880,6 +880,10 @@ wasm_runtime_set_native_stack_boundary(WASMExecEnv *exec_env,
WASM_RUNTIME_API_EXTERN void WASM_RUNTIME_API_EXTERN void
wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env, wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env,
int instructions_to_execute); int instructions_to_execute);
/* See wasm_export.h for description */
WASM_RUNTIME_API_EXTERN bool
wasm_runtime_resume_wasm(WASMExecEnv *exec_env);
#endif #endif
#if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0 #if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0

View File

@ -1928,6 +1928,26 @@ WASM_RUNTIME_API_EXTERN void
wasm_runtime_set_instruction_count_limit(wasm_exec_env_t exec_env, wasm_runtime_set_instruction_count_limit(wasm_exec_env_t exec_env,
int instruction_count); 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: * Dump runtime memory consumption, including:
* Exec env memory consumption * Exec env memory consumption

View File

@ -1558,6 +1558,37 @@ get_global_addr(uint8 *global_data, WASMGlobalInstance *global)
#define CHECK_INSTRUCTION_LIMIT() (void)0 #define CHECK_INSTRUCTION_LIMIT() (void)0
#endif #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 static void
wasm_interp_call_func_bytecode(WASMModuleInstance *module, wasm_interp_call_func_bytecode(WASMModuleInstance *module,
WASMExecEnv *exec_env, WASMExecEnv *exec_env,
@ -1671,6 +1702,16 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
#undef HANDLE_OPCODE #undef HANDLE_OPCODE
#endif #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 #if WASM_ENABLE_LABELS_AS_VALUES == 0
while (frame_ip < frame_ip_end) { while (frame_ip < frame_ip_end) {
opcode = *frame_ip++; opcode = *frame_ip++;
@ -6857,6 +6898,9 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
wasm_exec_env_set_cur_frame(exec_env, frame); wasm_exec_env_set_cur_frame(exec_env, frame);
} }
#if WASM_ENABLE_INSTRUCTION_METERING != 0
resume_func:
#endif
#if WASM_ENABLE_THREAD_MGR != 0 #if WASM_ENABLE_THREAD_MGR != 0
CHECK_SUSPEND_FLAGS(); CHECK_SUSPEND_FLAGS();
#endif #endif
@ -7413,6 +7457,10 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
wasm_runtime_get_running_mode((WASMModuleInstanceCommon *)module_inst); wasm_runtime_get_running_mode((WASMModuleInstanceCommon *)module_inst);
/* Allocate sufficient cells for all kinds of return values. */ /* Allocate sufficient cells for all kinds of return values. */
bool alloc_frame = true; 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) { if (argc < function->param_cell_num) {
char buf[128]; char buf[128];
@ -7456,7 +7504,34 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
#endif #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 = unsigned all_cell_num =
function->ret_cell_num > 2 ? function->ret_cell_num : 2; function->ret_cell_num > 2 ? function->ret_cell_num : 2;
unsigned frame_size; unsigned frame_size;
@ -7513,7 +7588,13 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
else { else {
if (running_mode == Mode_Interp) { if (running_mode == Mode_Interp) {
wasm_interp_call_func_bytecode(module_inst, exec_env, function, 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 #if WASM_ENABLE_FAST_JIT != 0
else if (running_mode == Mode_Fast_JIT) { else if (running_mode == Mode_Fast_JIT) {
@ -7554,6 +7635,27 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
#endif #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 */ /* Output the return value to the caller */
if (!wasm_copy_exception(module_inst, NULL)) { if (!wasm_copy_exception(module_inst, NULL)) {
if (alloc_frame) { 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); wasm_exec_env_set_cur_frame(exec_env, prev_frame);
FREE_FRAME(exec_env, frame); FREE_FRAME(exec_env, frame);
} }
#if WASM_ENABLE_INSTRUCTION_METERING != 0
clear_metering_suspend_state(exec_env);
#endif
} }

View File

@ -90,8 +90,21 @@ typedef float64 CellType_F64;
} while (0) } while (0)
#if WASM_ENABLE_INSTRUCTION_METERING != 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() \ #define CHECK_INSTRUCTION_LIMIT() \
if (instructions_left == 0) { \ if (instructions_left == 0) { \
ROLLBACK_IP_AFTER_METERING_CHECK(); \
wasm_set_exception(module, "instruction limit exceeded"); \ wasm_set_exception(module, "instruction limit exceeded"); \
goto got_exception; \ goto got_exception; \
} \ } \
@ -102,6 +115,37 @@ typedef float64 CellType_F64;
#define CHECK_INSTRUCTION_LIMIT() (void)0 #define CHECK_INSTRUCTION_LIMIT() (void)0
#endif #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 static inline uint32
rotl32(uint32 n, uint32 c) rotl32(uint32 n, uint32 c)
{ {
@ -1594,6 +1638,16 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
} }
#endif #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 #if WASM_ENABLE_LABELS_AS_VALUES == 0
while (frame_ip < frame_ip_end) { while (frame_ip < frame_ip_end) {
opcode = *frame_ip++; opcode = *frame_ip++;
@ -7788,6 +7842,14 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
HANDLE_OP_END(); 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: return_func:
{ {
FREE_FRAME(exec_env, frame); FREE_FRAME(exec_env, frame);
@ -7888,13 +7950,17 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
WASMFunctionInstance *function, uint32 argc, WASMFunctionInstance *function, uint32 argc,
uint32 argv[]) uint32 argv[])
{ {
WASMRuntimeFrame *prev_frame = wasm_exec_env_get_cur_frame(exec_env); WASMRuntimeFrame *frame = NULL, *prev_frame, *outs_area;
WASMInterpFrame *frame, *outs_area;
/* Allocate sufficient cells for all kinds of return values. */ /* Allocate sufficient cells for all kinds of return values. */
unsigned all_cell_num = unsigned all_cell_num =
function->ret_cell_num > 2 ? function->ret_cell_num : 2, function->ret_cell_num > 2 ? function->ret_cell_num : 2,
i; 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 /* This frame won't be used by JITed code, so only allocate interp
frame here. */ frame here. */
unsigned frame_size; unsigned frame_size;
@ -7927,8 +7993,38 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
} }
#endif #endif
if (!(frame = prev_frame = wasm_exec_env_get_cur_frame(exec_env);
ALLOC_FRAME(exec_env, frame_size, (WASMInterpFrame *)prev_frame)))
#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; return;
outs_area = wasm_exec_env_wasm_stack_top(exec_env); outs_area = wasm_exec_env_wasm_stack_top(exec_env);
@ -7939,7 +8035,8 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
#if WASM_ENABLE_GC != 0 #if WASM_ENABLE_GC != 0
frame->frame_ref = frame->frame_ref =
(uint8 *)(frame->lp (uint8 *)(frame->lp
+ (function->ret_cell_num > 2 ? function->ret_cell_num : 2)); + (function->ret_cell_num > 2 ? function->ret_cell_num
: 2));
#endif #endif
frame->ret_offset = 0; frame->ret_offset = 0;
@ -7951,9 +8048,11 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
} }
if (argc > 0) if (argc > 0)
word_copy(outs_area->operand + function->const_cell_num, argv, argc); 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) #if defined(os_writegsbase)
{ {
@ -7980,9 +8079,35 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
} }
} }
else { 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 */ /* Output the return value to the caller */
if (!wasm_copy_exception(module_inst, NULL)) { if (!wasm_copy_exception(module_inst, NULL)) {
for (i = 0; i < function->ret_cell_num; i++) for (i = 0; i < function->ret_cell_num; i++)
@ -7996,8 +8121,14 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
#endif #endif
} }
if (alloc_frame) {
wasm_exec_env_set_cur_frame(exec_env, prev_frame); wasm_exec_env_set_cur_frame(exec_env, prev_frame);
FREE_FRAME(exec_env, 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 #if WASM_ENABLE_OPCODE_COUNTER != 0
wasm_interp_dump_op_count(); wasm_interp_dump_op_count();
#endif #endif

View File

@ -620,6 +620,12 @@ SIMDE (SIMD Everywhere) implements SIMD operations in fast interpreter mode.
> [!NOTE] > [!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. > 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] > [!WARNING]
> This is only supported in classic interpreter mode. > This is only supported in classic interpreter mode.

View File

@ -90,6 +90,7 @@ add_subdirectory(tid-allocator)
add_subdirectory(unsupported-features) add_subdirectory(unsupported-features)
add_subdirectory(exception-handling) add_subdirectory(exception-handling)
add_subdirectory(running-modes) add_subdirectory(running-modes)
add_subdirectory(instruction-metering)
if(FULL_TEST) if(FULL_TEST)
message(STATUS "FULL_TEST=ON: include llm-enhanced-test") message(STATUS "FULL_TEST=ON: include llm-enhanced-test")

View File

@ -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)

View File

@ -0,0 +1,224 @@
#include "gtest/gtest.h"
#include "test_helper.h"
#include <vector>
#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<uint8_t>
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<uint8_t> 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);
}

View File

@ -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 .)

View File

@ -0,0 +1,17 @@
#include <stdint.h>
int32_t
countdown(int32_t n)
{
while (n > 0) {
n = n - 1;
}
return n;
}
int32_t
noop(void)
{
return 7;
}

View File

@ -0,0 +1,23 @@
#include <stdint.h>
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;
}