Skip to content

Commit 1809d7a

Browse files
committed
feat(instruction-metering): implement instruction metering resume functionality and add tests
1 parent f0aa4e8 commit 1809d7a

File tree

10 files changed

+426
-28
lines changed

10 files changed

+426
-28
lines changed

core/iwasm/common/wasm_exec_env.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ wasm_exec_env_create_internal(struct WASMModuleInstanceCommon *module_inst,
8787

8888
#if WASM_ENABLE_INSTRUCTION_METERING != 0
8989
exec_env->instructions_to_execute = -1;
90+
exec_env->metering_suspended = false;
91+
exec_env->metering_suspend_frame = NULL;
9092
#endif
9193

9294
return exec_env;

core/iwasm/common/wasm_exec_env.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ typedef struct WASMExecEnv {
9090
#if WASM_ENABLE_INSTRUCTION_METERING != 0
9191
/* instructions to execute */
9292
int instructions_to_execute;
93+
94+
/* true when classic interpreter suspended by instruction metering */
95+
bool metering_suspended;
96+
97+
/* top frame to resume from when metering_suspended is true */
98+
struct WASMInterpFrame *metering_suspend_frame;
9399
#endif
94100

95101
#if WASM_ENABLE_FAST_JIT != 0

core/iwasm/interpreter/wasm_interp_classic.c

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,6 +1558,22 @@ get_global_addr(uint8 *global_data, WASMGlobalInstance *global)
15581558
#define CHECK_INSTRUCTION_LIMIT() (void)0
15591559
#endif
15601560

1561+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
1562+
static inline bool
1563+
is_instruction_metering_exception(WASMModuleInstance *module_inst)
1564+
{
1565+
const char *exception = wasm_get_exception(module_inst);
1566+
return exception && strstr(exception, "instruction limit exceeded");
1567+
}
1568+
1569+
static inline void
1570+
clear_metering_suspend_state(WASMExecEnv *exec_env)
1571+
{
1572+
exec_env->metering_suspended = false;
1573+
exec_env->metering_suspend_frame = NULL;
1574+
}
1575+
#endif
1576+
15611577
static void
15621578
wasm_interp_call_func_bytecode(WASMModuleInstance *module,
15631579
WASMExecEnv *exec_env,
@@ -1671,6 +1687,16 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
16711687
#undef HANDLE_OPCODE
16721688
#endif
16731689

1690+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
1691+
if (prev_frame && prev_frame->function == cur_func && prev_frame->ip) {
1692+
RECOVER_CONTEXT(prev_frame);
1693+
#if WASM_ENABLE_TAIL_CALL != 0 || WASM_ENABLE_GC != 0
1694+
is_return_call = false;
1695+
#endif
1696+
goto resume_func;
1697+
}
1698+
#endif
1699+
16741700
#if WASM_ENABLE_LABELS_AS_VALUES == 0
16751701
while (frame_ip < frame_ip_end) {
16761702
opcode = *frame_ip++;
@@ -6857,6 +6883,9 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
68576883

68586884
wasm_exec_env_set_cur_frame(exec_env, frame);
68596885
}
6886+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
6887+
resume_func:
6888+
#endif
68606889
#if WASM_ENABLE_THREAD_MGR != 0
68616890
CHECK_SUSPEND_FLAGS();
68626891
#endif
@@ -7413,6 +7442,10 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
74137442
wasm_runtime_get_running_mode((WASMModuleInstanceCommon *)module_inst);
74147443
/* Allocate sufficient cells for all kinds of return values. */
74157444
bool alloc_frame = true;
7445+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
7446+
bool resume_metering = false;
7447+
WASMRuntimeFrame *suspended_frame = NULL;
7448+
#endif
74167449

74177450
if (argc < function->param_cell_num) {
74187451
char buf[128];
@@ -7456,7 +7489,34 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
74567489
#endif
74577490
}
74587491

7459-
if (alloc_frame) {
7492+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
7493+
if (running_mode == Mode_Interp && exec_env->metering_suspended) {
7494+
suspended_frame = exec_env->metering_suspend_frame;
7495+
if (!suspended_frame || suspended_frame->function != function) {
7496+
wasm_set_exception(module_inst,
7497+
"cannot call different function while metering "
7498+
"resume is pending");
7499+
return;
7500+
}
7501+
if (!suspended_frame->prev_frame) {
7502+
wasm_set_exception(module_inst,
7503+
"invalid metering resume frame state");
7504+
clear_metering_suspend_state(exec_env);
7505+
return;
7506+
}
7507+
7508+
resume_metering = true;
7509+
frame = suspended_frame->prev_frame;
7510+
prev_frame = frame->prev_frame;
7511+
wasm_exec_env_set_cur_frame(exec_env, suspended_frame);
7512+
}
7513+
#endif
7514+
7515+
if (alloc_frame
7516+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
7517+
&& !resume_metering
7518+
#endif
7519+
) {
74607520
unsigned all_cell_num =
74617521
function->ret_cell_num > 2 ? function->ret_cell_num : 2;
74627522
unsigned frame_size;
@@ -7513,7 +7573,13 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
75137573
else {
75147574
if (running_mode == Mode_Interp) {
75157575
wasm_interp_call_func_bytecode(module_inst, exec_env, function,
7516-
frame);
7576+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
7577+
resume_metering ? suspended_frame
7578+
: frame
7579+
#else
7580+
frame
7581+
#endif
7582+
);
75177583
}
75187584
#if WASM_ENABLE_FAST_JIT != 0
75197585
else if (running_mode == Mode_Fast_JIT) {
@@ -7554,6 +7620,15 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
75547620
#endif
75557621
}
75567622

7623+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
7624+
if ((running_mode == Mode_Interp)
7625+
&& is_instruction_metering_exception(module_inst)) {
7626+
exec_env->metering_suspended = true;
7627+
exec_env->metering_suspend_frame = wasm_exec_env_get_cur_frame(exec_env);
7628+
return;
7629+
}
7630+
#endif
7631+
75577632
/* Output the return value to the caller */
75587633
if (!wasm_copy_exception(module_inst, NULL)) {
75597634
if (alloc_frame) {
@@ -7575,4 +7650,8 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
75757650
wasm_exec_env_set_cur_frame(exec_env, prev_frame);
75767651
FREE_FRAME(exec_env, frame);
75777652
}
7653+
7654+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
7655+
clear_metering_suspend_state(exec_env);
7656+
#endif
75787657
}

core/iwasm/interpreter/wasm_interp_fast.c

Lines changed: 131 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,21 @@ typedef float64 CellType_F64;
9090
} while (0)
9191

9292
#if WASM_ENABLE_INSTRUCTION_METERING != 0
93+
#if WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS != 0
94+
#define ROLLBACK_IP_AFTER_METERING_CHECK() \
95+
do { \
96+
frame_ip -= sizeof(void *); \
97+
} while (0)
98+
#else
99+
#define ROLLBACK_IP_AFTER_METERING_CHECK() \
100+
do { \
101+
frame_ip -= sizeof(int32); \
102+
} while (0)
103+
#endif
104+
93105
#define CHECK_INSTRUCTION_LIMIT() \
94106
if (instructions_left == 0) { \
107+
ROLLBACK_IP_AFTER_METERING_CHECK(); \
95108
wasm_set_exception(module, "instruction limit exceeded"); \
96109
goto got_exception; \
97110
} \
@@ -102,6 +115,22 @@ typedef float64 CellType_F64;
102115
#define CHECK_INSTRUCTION_LIMIT() (void)0
103116
#endif
104117

118+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
119+
static inline bool
120+
is_instruction_metering_exception(WASMModuleInstance *module_inst)
121+
{
122+
const char *exception = wasm_get_exception(module_inst);
123+
return exception && strstr(exception, "instruction limit exceeded");
124+
}
125+
126+
static inline void
127+
clear_metering_suspend_state(WASMExecEnv *exec_env)
128+
{
129+
exec_env->metering_suspended = false;
130+
exec_env->metering_suspend_frame = NULL;
131+
}
132+
#endif
133+
105134
static inline uint32
106135
rotl32(uint32 n, uint32 c)
107136
{
@@ -1594,6 +1623,16 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
15941623
}
15951624
#endif
15961625

1626+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
1627+
if (prev_frame && prev_frame->function == cur_func && prev_frame->ip) {
1628+
RECOVER_CONTEXT(prev_frame);
1629+
#if WASM_ENABLE_TAIL_CALL != 0 || WASM_ENABLE_GC != 0
1630+
is_return_call = false;
1631+
#endif
1632+
goto resume_func;
1633+
}
1634+
#endif
1635+
15971636
#if WASM_ENABLE_LABELS_AS_VALUES == 0
15981637
while (frame_ip < frame_ip_end) {
15991638
opcode = *frame_ip++;
@@ -7788,6 +7827,14 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
77887827
HANDLE_OP_END();
77897828
}
77907829

7830+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
7831+
resume_func:
7832+
#if WASM_ENABLE_THREAD_MGR != 0
7833+
CHECK_SUSPEND_FLAGS();
7834+
#endif
7835+
HANDLE_OP_END();
7836+
#endif
7837+
77917838
return_func:
77927839
{
77937840
FREE_FRAME(exec_env, frame);
@@ -7888,13 +7935,17 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
78887935
WASMFunctionInstance *function, uint32 argc,
78897936
uint32 argv[])
78907937
{
7891-
WASMRuntimeFrame *prev_frame = wasm_exec_env_get_cur_frame(exec_env);
7892-
WASMInterpFrame *frame, *outs_area;
7938+
WASMRuntimeFrame *frame = NULL, *prev_frame, *outs_area;
78937939

78947940
/* Allocate sufficient cells for all kinds of return values. */
78957941
unsigned all_cell_num =
78967942
function->ret_cell_num > 2 ? function->ret_cell_num : 2,
78977943
i;
7944+
bool alloc_frame = true;
7945+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
7946+
bool resume_metering = false;
7947+
WASMRuntimeFrame *suspended_frame = NULL;
7948+
#endif
78987949
/* This frame won't be used by JITed code, so only allocate interp
78997950
frame here. */
79007951
unsigned frame_size;
@@ -7927,33 +7978,66 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
79277978
}
79287979
#endif
79297980

7930-
if (!(frame =
7931-
ALLOC_FRAME(exec_env, frame_size, (WASMInterpFrame *)prev_frame)))
7932-
return;
7981+
prev_frame = wasm_exec_env_get_cur_frame(exec_env);
79337982

7934-
outs_area = wasm_exec_env_wasm_stack_top(exec_env);
7935-
frame->function = NULL;
7936-
frame->ip = NULL;
7937-
/* There is no local variable. */
7938-
frame->lp = frame->operand + 0;
7983+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
7984+
if (exec_env->metering_suspended) {
7985+
suspended_frame = exec_env->metering_suspend_frame;
7986+
if (!suspended_frame || suspended_frame->function != function) {
7987+
wasm_set_exception(module_inst,
7988+
"cannot call different function while metering "
7989+
"resume is pending");
7990+
return;
7991+
}
7992+
if (!suspended_frame->prev_frame) {
7993+
wasm_set_exception(module_inst,
7994+
"invalid metering resume frame state");
7995+
clear_metering_suspend_state(exec_env);
7996+
return;
7997+
}
7998+
7999+
resume_metering = true;
8000+
frame = suspended_frame->prev_frame;
8001+
prev_frame = frame->prev_frame;
8002+
wasm_exec_env_set_cur_frame(exec_env, suspended_frame);
8003+
}
8004+
#endif
8005+
8006+
if (alloc_frame
8007+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
8008+
&& !resume_metering
8009+
#endif
8010+
) {
8011+
if (!(frame = ALLOC_FRAME(exec_env, frame_size,
8012+
(WASMInterpFrame *)prev_frame)))
8013+
return;
8014+
8015+
outs_area = wasm_exec_env_wasm_stack_top(exec_env);
8016+
frame->function = NULL;
8017+
frame->ip = NULL;
8018+
/* There is no local variable. */
8019+
frame->lp = frame->operand + 0;
79398020
#if WASM_ENABLE_GC != 0
7940-
frame->frame_ref =
7941-
(uint8 *)(frame->lp
7942-
+ (function->ret_cell_num > 2 ? function->ret_cell_num : 2));
8021+
frame->frame_ref =
8022+
(uint8 *)(frame->lp + (function->ret_cell_num > 2
8023+
? function->ret_cell_num
8024+
: 2));
79438025
#endif
7944-
frame->ret_offset = 0;
8026+
frame->ret_offset = 0;
79458027

7946-
if ((uint8 *)(outs_area->operand + function->const_cell_num + argc)
7947-
> exec_env->wasm_stack.top_boundary) {
7948-
wasm_set_exception((WASMModuleInstance *)exec_env->module_inst,
7949-
"wasm operand stack overflow");
7950-
return;
7951-
}
8028+
if ((uint8 *)(outs_area->operand + function->const_cell_num + argc)
8029+
> exec_env->wasm_stack.top_boundary) {
8030+
wasm_set_exception((WASMModuleInstance *)exec_env->module_inst,
8031+
"wasm operand stack overflow");
8032+
return;
8033+
}
79528034

7953-
if (argc > 0)
7954-
word_copy(outs_area->operand + function->const_cell_num, argv, argc);
8035+
if (argc > 0)
8036+
word_copy(outs_area->operand + function->const_cell_num, argv,
8037+
argc);
79558038

7956-
wasm_exec_env_set_cur_frame(exec_env, frame);
8039+
wasm_exec_env_set_cur_frame(exec_env, frame);
8040+
}
79578041

79588042
#if defined(os_writegsbase)
79598043
{
@@ -7980,9 +8064,24 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
79808064
}
79818065
}
79828066
else {
7983-
wasm_interp_call_func_bytecode(module_inst, exec_env, function, frame);
8067+
wasm_interp_call_func_bytecode(module_inst, exec_env, function,
8068+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
8069+
resume_metering ? suspended_frame
8070+
: frame
8071+
#else
8072+
frame
8073+
#endif
8074+
);
79848075
}
79858076

8077+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
8078+
if (is_instruction_metering_exception(module_inst)) {
8079+
exec_env->metering_suspended = true;
8080+
exec_env->metering_suspend_frame = wasm_exec_env_get_cur_frame(exec_env);
8081+
return;
8082+
}
8083+
#endif
8084+
79868085
/* Output the return value to the caller */
79878086
if (!wasm_copy_exception(module_inst, NULL)) {
79888087
for (i = 0; i < function->ret_cell_num; i++)
@@ -7996,8 +8095,14 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env,
79968095
#endif
79978096
}
79988097

7999-
wasm_exec_env_set_cur_frame(exec_env, prev_frame);
8000-
FREE_FRAME(exec_env, frame);
8098+
if (alloc_frame) {
8099+
wasm_exec_env_set_cur_frame(exec_env, prev_frame);
8100+
FREE_FRAME(exec_env, frame);
8101+
}
8102+
8103+
#if WASM_ENABLE_INSTRUCTION_METERING != 0
8104+
clear_metering_suspend_state(exec_env);
8105+
#endif
80018106
#if WASM_ENABLE_OPCODE_COUNTER != 0
80028107
wasm_interp_dump_op_count();
80038108
#endif

doc/build_wamr.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,12 @@ SIMDE (SIMD Everywhere) implements SIMD operations in fast interpreter mode.
612612
613613
> [!NOTE]
614614
> 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.
615+
>
616+
> In classic and fast interpreter modes, when instruction budget is exhausted,
617+
> the runtime raises `instruction limit exceeded`. If the host sets a new budget
618+
> and calls the same function again (optionally after `wasm_runtime_clear_exception(...)`),
619+
> execution resumes from preserved interpreter state instead of restarting from
620+
> function entry.
615621
616622
> [!WARNING]
617623
> This is only supported in classic interpreter mode.

0 commit comments

Comments
 (0)