mirror of
https://github.com/bytecodealliance/wasm-micro-runtime.git
synced 2026-04-18 10:17:38 +00:00
feat(instruction-metering): implement instruction metering resume functionality and add tests
This commit is contained in:
parent
f0aa4e8643
commit
41dd8a7a49
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 *
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1558,6 +1558,25 @@ 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;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
wasm_interp_call_func_bytecode(WASMModuleInstance *module,
|
||||
WASMExecEnv *exec_env,
|
||||
|
|
@ -1671,6 +1690,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 +6886,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 +7445,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 +7492,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;
|
||||
}
|
||||
if (!suspended_frame->prev_frame) {
|
||||
wasm_set_exception(module_inst,
|
||||
"invalid metering resume frame state");
|
||||
clear_metering_suspend_state(exec_env);
|
||||
return;
|
||||
}
|
||||
|
||||
resume_metering = true;
|
||||
frame = suspended_frame->prev_frame;
|
||||
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 +7576,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 +7623,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 +7665,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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,25 @@ 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;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline uint32
|
||||
rotl32(uint32 n, uint32 c)
|
||||
{
|
||||
|
|
@ -1594,6 +1626,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 +7830,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 +7938,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 +7981,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_GC != 0
|
||||
frame->frame_ref =
|
||||
(uint8 *)(frame->lp
|
||||
+ (function->ret_cell_num > 2 ? function->ret_cell_num : 2));
|
||||
#endif
|
||||
frame->ret_offset = 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;
|
||||
}
|
||||
if (!suspended_frame->prev_frame) {
|
||||
wasm_set_exception(module_inst,
|
||||
"invalid metering resume frame state");
|
||||
clear_metering_suspend_state(exec_env);
|
||||
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;
|
||||
resume_metering = true;
|
||||
frame = suspended_frame->prev_frame;
|
||||
prev_frame = frame->prev_frame;
|
||||
wasm_exec_env_set_cur_frame(exec_env, suspended_frame);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (argc > 0)
|
||||
word_copy(outs_area->operand + function->const_cell_num, argv, argc);
|
||||
if (alloc_frame
|
||||
#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
&& !resume_metering
|
||||
#endif
|
||||
) {
|
||||
if (!(frame = ALLOC_FRAME(exec_env, frame_size,
|
||||
(WASMInterpFrame *)prev_frame)))
|
||||
return;
|
||||
|
||||
wasm_exec_env_set_cur_frame(exec_env, frame);
|
||||
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));
|
||||
#endif
|
||||
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 (argc > 0)
|
||||
word_copy(outs_area->operand + function->const_cell_num, argv,
|
||||
argc);
|
||||
|
||||
wasm_exec_env_set_cur_frame(exec_env, frame);
|
||||
}
|
||||
|
||||
#if defined(os_writegsbase)
|
||||
{
|
||||
|
|
@ -7980,9 +8067,35 @@ 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)) {
|
||||
for (i = 0; i < function->ret_cell_num; i++)
|
||||
|
|
@ -7996,8 +8109,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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
54
tests/unit/instruction-metering/CMakeLists.txt
Normal file
54
tests/unit/instruction-metering/CMakeLists.txt
Normal 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)
|
||||
|
|
@ -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);
|
||||
}
|
||||
18
tests/unit/instruction-metering/wasm-apps/CMakeLists.txt
Normal file
18
tests/unit/instruction-metering/wasm-apps/CMakeLists.txt
Normal 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 .)
|
||||
17
tests/unit/instruction-metering/wasm-apps/resume_counter.c
Normal file
17
tests/unit/instruction-metering/wasm-apps/resume_counter.c
Normal 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;
|
||||
}
|
||||
23
tests/unit/instruction-metering/wasm-apps/resume_nested.c
Normal file
23
tests/unit/instruction-metering/wasm-apps/resume_nested.c
Normal 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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user