Просмотр исходного кода

feat: Add instruction metering for interpreter (#4122)

- add instruction metering support with execution limit
- initialize instruction execution limit in exec_env
- docs: add instruction metering section to build_wamr documentation
Alix ANNERAUD 7 месяцев назад
Родитель
Сommit
c018b8ab98

+ 4 - 0
build-scripts/config_common.cmake

@@ -671,6 +671,10 @@ if (WAMR_BUILD_AOT_VALIDATOR EQUAL 1)
   message ("     AOT validator enabled")
   add_definitions (-DWASM_ENABLE_AOT_VALIDATOR=1)
 endif ()
+if (WAMR_BUILD_INSTRUCTION_METERING EQUAL 1)
+  message ("     Instruction metering enabled")
+  add_definitions (-DWASM_ENABLE_INSTRUCTION_METERING=1)
+endif ()
 
 ########################################
 # Show Phase4 Wasm proposals status.

+ 4 - 0
core/config.h

@@ -716,4 +716,8 @@ unless used elsewhere */
 #define WASM_ENABLE_AOT_VALIDATOR 0
 #endif
 
+#ifndef WASM_ENABLE_INSTRUCTION_METERING
+#define WASM_ENABLE_INSTRUCTION_METERING 0
+#endif
+
 #endif /* end of _CONFIG_H_ */

+ 4 - 0
core/iwasm/common/wasm_exec_env.c

@@ -85,6 +85,10 @@ wasm_exec_env_create_internal(struct WASMModuleInstanceCommon *module_inst,
     wasm_runtime_dump_exec_env_mem_consumption(exec_env);
 #endif
 
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+    exec_env->instructions_to_execute = -1;
+#endif
+
     return exec_env;
 
 #ifdef OS_ENABLE_HW_BOUND_CHECK

+ 5 - 0
core/iwasm/common/wasm_exec_env.h

@@ -87,6 +87,11 @@ typedef struct WASMExecEnv {
         uint8 *bottom;
     } wasm_stack;
 
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+    /* instructions to execute */
+    int instructions_to_execute;
+#endif
+
 #if WASM_ENABLE_FAST_JIT != 0
     /**
      * Cache for

+ 9 - 0
core/iwasm/common/wasm_runtime_common.c

@@ -2285,6 +2285,15 @@ wasm_runtime_access_exce_check_guard_page()
 }
 #endif
 
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+void
+wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env,
+                                         int instructions_to_execute)
+{
+    exec_env->instructions_to_execute = instructions_to_execute;
+}
+#endif
+
 WASMFuncType *
 wasm_runtime_get_function_type(const WASMFunctionInstanceCommon *function,
                                uint32 module_type)

+ 9 - 1
core/iwasm/common/wasm_runtime_common.h

@@ -791,9 +791,17 @@ WASM_RUNTIME_API_EXTERN void
 wasm_runtime_set_native_stack_boundary(WASMExecEnv *exec_env,
                                        uint8 *native_stack_boundary);
 
-#if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
 /* See wasm_export.h for description */
 WASM_RUNTIME_API_EXTERN void
+wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env,
+                                         int instructions_to_execute);
+#endif
+
+#if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0
+/* See wasm_export.h for description */
+WASM_RUNTIME_API_EXTERN
+void
 wasm_runtime_set_bounds_checks(WASMModuleInstanceCommon *module_inst,
                                bool enable);
 

+ 14 - 0
core/iwasm/include/wasm_export.h

@@ -1821,6 +1821,20 @@ WASM_RUNTIME_API_EXTERN void
 wasm_runtime_set_native_stack_boundary(wasm_exec_env_t exec_env,
                                        uint8_t *native_stack_boundary);
 
+/**
+ * Set the instruction count limit to the execution environment.
+ * By default the instruction count limit is -1, which means no limit.
+ * However, if the instruction count limit is set to a positive value,
+ * the execution will be terminated when the instruction count reaches
+ * the limit.
+ *
+ * @param exec_env the execution environment
+ * @param instruction_count the instruction count limit
+ */
+WASM_RUNTIME_API_EXTERN void
+wasm_runtime_set_instruction_count_limit(wasm_exec_env_t exec_env,
+                                         int instruction_count);
+
 /**
  * Dump runtime memory consumption, including:
  *     Exec env memory consumption

+ 28 - 2
core/iwasm/interpreter/wasm_interp_classic.c

@@ -1516,10 +1516,13 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst,
             }                                                                 \
             os_mutex_unlock(&exec_env->wait_lock);                            \
         }                                                                     \
+        CHECK_INSTRUCTION_LIMIT();                                            \
         goto *handle_table[*frame_ip++];                                      \
     } while (0)
 #else
-#define HANDLE_OP_END() FETCH_OPCODE_AND_DISPATCH()
+#define HANDLE_OP_END()        \
+    CHECK_INSTRUCTION_LIMIT(); \
+    FETCH_OPCODE_AND_DISPATCH()
 #endif
 
 #else /* else of WASM_ENABLE_LABELS_AS_VALUES */
@@ -1542,9 +1545,12 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst,
         }                                                                 \
         os_mutex_unlock(&exec_env->wait_lock);                            \
     }                                                                     \
+    CHECK_INSTRUCTION_LIMIT();                                            \
     continue;
 #else
-#define HANDLE_OP_END() continue
+#define HANDLE_OP_END()        \
+    CHECK_INSTRUCTION_LIMIT(); \
+    continue;
 #endif
 
 #endif /* end of WASM_ENABLE_LABELS_AS_VALUES */
@@ -1562,6 +1568,18 @@ get_global_addr(uint8 *global_data, WASMGlobalInstance *global)
 #endif
 }
 
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+#define CHECK_INSTRUCTION_LIMIT()                                 \
+    if (instructions_left == 0) {                                 \
+        wasm_set_exception(module, "instruction limit exceeded"); \
+        goto got_exception;                                       \
+    }                                                             \
+    else if (instructions_left > 0)                               \
+        instructions_left--;
+#else
+#define CHECK_INSTRUCTION_LIMIT() (void)0
+#endif
+
 static void
 wasm_interp_call_func_bytecode(WASMModuleInstance *module,
                                WASMExecEnv *exec_env,
@@ -1605,6 +1623,14 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
     uint32 local_idx, local_offset, global_idx;
     uint8 local_type, *global_addr;
     uint32 cache_index, type_index, param_cell_num, cell_num;
+
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+    int instructions_left = -1;
+    if (exec_env) {
+        instructions_left = exec_env->instructions_to_execute;
+    }
+#endif
+
 #if WASM_ENABLE_EXCE_HANDLING != 0
     int32_t exception_tag_index;
 #endif

+ 23 - 0
core/iwasm/interpreter/wasm_interp_fast.c

@@ -105,6 +105,19 @@ typedef float64 CellType_F64;
             goto unaligned_atomic;                 \
     } while (0)
 
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+#define CHECK_INSTRUCTION_LIMIT()                                 \
+    if (instructions_left == 0) {                                 \
+        wasm_set_exception(module, "instruction limit exceeded"); \
+        goto got_exception;                                       \
+    }                                                             \
+    else if (instructions_left > 0)                               \
+        instructions_left--;
+
+#else
+#define CHECK_INSTRUCTION_LIMIT() (void)0
+#endif
+
 static inline uint32
 rotl32(uint32 n, uint32 c)
 {
@@ -1441,6 +1454,7 @@ wasm_interp_dump_op_count()
     do {                                               \
         const void *p_label_addr = *(void **)frame_ip; \
         frame_ip += sizeof(void *);                    \
+        CHECK_INSTRUCTION_LIMIT();                     \
         goto *p_label_addr;                            \
     } while (0)
 #else
@@ -1452,6 +1466,7 @@ wasm_interp_dump_op_count()
         /* int32 relative offset was emitted in 64-bit target */          \
         p_label_addr = label_base + (int32)LOAD_U32_WITH_2U16S(frame_ip); \
         frame_ip += sizeof(int32);                                        \
+        CHECK_INSTRUCTION_LIMIT();                                        \
         goto *p_label_addr;                                               \
     } while (0)
 #else
@@ -1462,6 +1477,7 @@ wasm_interp_dump_op_count()
         /* uint32 label address was emitted in 32-bit target */          \
         p_label_addr = (void *)(uintptr_t)LOAD_U32_WITH_2U16S(frame_ip); \
         frame_ip += sizeof(int32);                                       \
+        CHECK_INSTRUCTION_LIMIT();                                       \
         goto *p_label_addr;                                              \
     } while (0)
 #endif
@@ -1538,6 +1554,13 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
     uint8 *maddr = NULL;
     uint32 local_idx, local_offset, global_idx;
     uint8 opcode = 0, local_type, *global_addr;
+
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+    int instructions_left = -1;
+    if (exec_env) {
+        instructions_left = exec_env->instructions_to_execute;
+    }
+#endif
 #if !defined(OS_ENABLE_HW_BOUND_CHECK) \
     || WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS == 0
 #if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0

+ 4 - 0
doc/build_wamr.md

@@ -327,6 +327,10 @@ And the wasm app can calls below APIs to allocate/free memory from/to the shared
 - **WAMR_BUILD_SHRUNK_MEMORY**=1/0, default to enable if not set
 > Note: When enabled, this feature will reduce memory usage by decreasing the size of the linear memory, particularly when the `memory.grow` opcode is not used and memory usage is somewhat predictable.
 
+## **Instruction metering**
+- **WAMR_BUILD_INSTRUCTION_METERING**=1/0, default to disable if not set
+> Note: Enabling this feature allows limiting the number of instructions a wasm module instance can execute. Use the `wasm_runtime_set_instruction_count_limit(...)` API before calling `wasm_runtime_call_*(...)` APIs to enforce this limit.
+
 ## **Combination of configurations:**
 
 We can combine the configurations. For example, if we want to disable interpreter, enable AOT and WASI, we can run command: