diff --git a/lib/fizzy/execute.cpp b/lib/fizzy/execute.cpp index dae5ede28..ef7d7d61e 100644 --- a/lib/fizzy/execute.cpp +++ b/lib/fizzy/execute.cpp @@ -5,6 +5,7 @@ #include "execute.hpp" #include "asserts.hpp" #include "cxx20/bit.hpp" +#include "instructions.hpp" #include "stack.hpp" #include "trunc_boundaries.hpp" #include "types.hpp" @@ -524,6 +525,11 @@ void branch(const Code& code, OperandStack& stack, const uint8_t*& pc, uint32_t stack.drop(stack_drop); } +template +ExecutionResult execute( + Instance& instance, FuncIdx func_idx, const Value* args, ExecutionContext& ctx) noexcept; + +template inline bool invoke_function(const FuncType& func_type, uint32_t func_idx, Instance& instance, OperandStack& stack, ExecutionContext& ctx) noexcept { @@ -531,7 +537,7 @@ inline bool invoke_function(const FuncType& func_type, uint32_t func_idx, Instan assert(stack.size() >= num_args); const auto call_args = stack.rend() - num_args; - const auto ret = execute(instance, func_idx, call_args, ctx); + const auto ret = execute(instance, func_idx, call_args, ctx); // Bubble up traps if (ret.trapped) return false; @@ -549,8 +555,7 @@ inline bool invoke_function(const FuncType& func_type, uint32_t func_idx, Instan return true; } -} // namespace - +template ExecutionResult execute( Instance& instance, FuncIdx func_idx, const Value* args, ExecutionContext& ctx) noexcept { @@ -574,9 +579,19 @@ ExecutionResult execute( const uint8_t* pc = code.instructions.data(); + [[maybe_unused]] const auto* cost_table = get_instruction_cost_table(); + while (true) { - const auto instruction = static_cast(*pc++); + const auto opcode = *pc++; + const auto instruction = static_cast(opcode); + + if constexpr (MeteringEnabled) + { + if ((ctx.ticks -= cost_table[opcode]) < 0) + goto trap; + } + switch (instruction) { case Instr::unreachable: @@ -647,7 +662,8 @@ ExecutionResult execute( const auto called_func_idx = read(pc); const auto& called_func_type = instance.module->get_function_type(called_func_idx); - if (!invoke_function(called_func_type, called_func_idx, instance, stack, ctx)) + if (!invoke_function( + called_func_type, called_func_idx, instance, stack, ctx)) goto trap; break; } @@ -673,7 +689,7 @@ ExecutionResult execute( if (expected_type != actual_type) goto trap; - if (!invoke_function( + if (!invoke_function( actual_type, called_func.func_idx, *called_func.instance, stack, ctx)) goto trap; break; @@ -1586,4 +1602,21 @@ ExecutionResult execute( trap: return Trap; } +} // namespace + +ExecutionResult execute( + Instance& instance, FuncIdx func_idx, const Value* args, ExecutionContext& ctx) noexcept +{ + if (ctx.metering_enabled) + return execute(instance, func_idx, args, ctx); + else + return execute(instance, func_idx, args, ctx); +} + +ExecutionResult execute(Instance& instance, FuncIdx func_idx, const Value* args) noexcept +{ + ExecutionContext ctx; + return execute(instance, func_idx, args, ctx); +} + } // namespace fizzy diff --git a/lib/fizzy/execute.hpp b/lib/fizzy/execute.hpp index 680f513c8..87e6b25f4 100644 --- a/lib/fizzy/execute.hpp +++ b/lib/fizzy/execute.hpp @@ -56,11 +56,8 @@ constexpr ExecutionResult Trap{false}; ExecutionResult execute( Instance& instance, FuncIdx func_idx, const Value* args, ExecutionContext& ctx) noexcept; -/// Execute a function from an instance with execution context starting with default depth of 0. +/// Execute a function from an instance with execution context starting with default depth of 0 and +/// metering disabled. /// Arguments and behavior is the same as in the other execute(). -inline ExecutionResult execute(Instance& instance, FuncIdx func_idx, const Value* args) noexcept -{ - ExecutionContext ctx; - return execute(instance, func_idx, args, ctx); -} +ExecutionResult execute(Instance& instance, FuncIdx func_idx, const Value* args) noexcept; } // namespace fizzy diff --git a/lib/fizzy/execution_context.hpp b/lib/fizzy/execution_context.hpp index 0b9be912d..84341aac5 100644 --- a/lib/fizzy/execution_context.hpp +++ b/lib/fizzy/execution_context.hpp @@ -4,6 +4,8 @@ #pragma once +#include + namespace fizzy { /// The storage for information shared by calls in the same execution "thread". @@ -33,6 +35,12 @@ class ExecutionContext public: int depth = 0; ///< Current call depth. + /// Current ticks left for execution, if #metering_enabled is true. + /// Execution traps when running out of ticks. + /// Ignored if #metering_enabled is false. + int64_t ticks = std::numeric_limits::max(); + /// Set to true to enable execution metering. + bool metering_enabled = false; /// Increments the call depth and returns the local call context which /// decrements the call depth back to the original value when going out of scope. diff --git a/lib/fizzy/instructions.cpp b/lib/fizzy/instructions.cpp index 68904d6c3..c03432d40 100644 --- a/lib/fizzy/instructions.cpp +++ b/lib/fizzy/instructions.cpp @@ -444,6 +444,222 @@ constexpr uint8_t instruction_max_align_table[256] = { /* f32_reinterpret_i32 = 0xbe */ 0, /* f64_reinterpret_i64 = 0xbf */ 0, }; + +constexpr int16_t instruction_cost_table[256] = { + // 5.4.1 Control instructions + /* unreachable = 0x00 */ 1, + /* nop = 0x01 */ 1, + /* block = 0x02 */ 1, + /* loop = 0x03 */ 1, + /* if_ = 0x04 */ 1, + /* else_ = 0x05 */ 1, + /* 0x06 */ 1, + /* 0x07 */ 1, + /* 0x08 */ 1, + /* 0x09 */ 1, + /* 0x0a */ 1, + /* end = 0x0b */ 1, + /* br = 0x0c */ 1, + /* br_if = 0x0d */ 1, + /* br_table = 0x0e */ 1, + /* return_ = 0x0f */ 1, + /* call = 0x10 */ 1, + /* call_indirect = 0x11 */ 1, + + /* 0x12 */ 1, + /* 0x13 */ 1, + /* 0x14 */ 1, + /* 0x15 */ 1, + /* 0x16 */ 1, + /* 0x17 */ 1, + /* 0x18 */ 1, + /* 0x19 */ 1, + + // 5.4.2 Parametric instructions + /* drop = 0x1a */ 1, + /* select = 0x1b */ 1, + + /* 0x1c */ 1, + /* 0x1d */ 1, + /* 0x1e */ 1, + /* 0x1f */ 1, + + // 5.4.3 Variable instructions + /* local_get = 0x20 */ 1, + /* local_set = 0x21 */ 1, + /* local_tee = 0x22 */ 1, + /* global_get = 0x23 */ 1, + /* global_set = 0x24 */ 1, + + /* 0x25 */ 1, + /* 0x26 */ 1, + /* 0x27 */ 1, + + // 5.4.4 Memory instructions + /* i32_load = 0x28 */ 1, + /* i64_load = 0x29 */ 1, + /* f32_load = 0x2a */ 1, + /* f64_load = 0x2b */ 1, + /* i32_load8_s = 0x2c */ 1, + /* i32_load8_u = 0x2d */ 1, + /* i32_load16_s = 0x2e */ 1, + /* i32_load16_u = 0x2f */ 1, + /* i64_load8_s = 0x30 */ 1, + /* i64_load8_u = 0x31 */ 1, + /* i64_load16_s = 0x32 */ 1, + /* i64_load16_u = 0x33 */ 1, + /* i64_load32_s = 0x34 */ 1, + /* i64_load32_u = 0x35 */ 1, + /* i32_store = 0x36 */ 1, + /* i64_store = 0x37 */ 1, + /* f32_store = 0x38 */ 1, + /* f64_store = 0x39 */ 1, + /* i32_store8 = 0x3a */ 1, + /* i32_store16 = 0x3b */ 1, + /* i64_store8 = 0x3c */ 1, + /* i64_store16 = 0x3d */ 1, + /* i64_store32 = 0x3e */ 1, + /* memory_size = 0x3f */ 1, + /* memory_grow = 0x40 */ 1, + + // 5.4.5 Numeric instructions + /* i32_const = 0x41 */ 1, + /* i64_const = 0x42 */ 1, + /* f32_const = 0x43 */ 1, + /* f64_const = 0x44 */ 1, + + /* i32_eqz = 0x45 */ 1, + /* i32_eq = 0x46 */ 1, + /* i32_ne = 0x47 */ 1, + /* i32_lt_s = 0x48 */ 1, + /* i32_lt_u = 0x49 */ 1, + /* i32_gt_s = 0x4a */ 1, + /* i32_gt_u = 0x4b */ 1, + /* i32_le_s = 0x4c */ 1, + /* i32_le_u = 0x4d */ 1, + /* i32_ge_s = 0x4e */ 1, + /* i32_ge_u = 0x4f */ 1, + + /* i64_eqz = 0x50 */ 1, + /* i64_eq = 0x51 */ 1, + /* i64_ne = 0x52 */ 1, + /* i64_lt_s = 0x53 */ 1, + /* i64_lt_u = 0x54 */ 1, + /* i64_gt_s = 0x55 */ 1, + /* i64_gt_u = 0x56 */ 1, + /* i64_le_s = 0x57 */ 1, + /* i64_le_u = 0x58 */ 1, + /* i64_ge_s = 0x59 */ 1, + /* i64_ge_u = 0x5a */ 1, + + /* f32_eq = 0x5b */ 1, + /* f32_ne = 0x5c */ 1, + /* f32_lt = 0x5d */ 1, + /* f32_gt = 0x5e */ 1, + /* f32_le = 0x5f */ 1, + /* f32_ge = 0x60 */ 1, + + /* f64_eq = 0x61 */ 1, + /* f64_ne = 0x62 */ 1, + /* f64_lt = 0x63 */ 1, + /* f64_gt = 0x64 */ 1, + /* f64_le = 0x65 */ 1, + /* f64_ge = 0x66 */ 1, + + /* i32_clz = 0x67 */ 1, + /* i32_ctz = 0x68 */ 1, + /* i32_popcnt = 0x69 */ 1, + /* i32_add = 0x6a */ 1, + /* i32_sub = 0x6b */ 1, + /* i32_mul = 0x6c */ 1, + /* i32_div_s = 0x6d */ 1, + /* i32_div_u = 0x6e */ 1, + /* i32_rem_s = 0x6f */ 1, + /* i32_rem_u = 0x70 */ 1, + /* i32_and = 0x71 */ 1, + /* i32_or = 0x72 */ 1, + /* i32_xor = 0x73 */ 1, + /* i32_shl = 0x74 */ 1, + /* i32_shr_s = 0x75 */ 1, + /* i32_shr_u = 0x76 */ 1, + /* i32_rotl = 0x77 */ 1, + /* i32_rotr = 0x78 */ 1, + + /* i64_clz = 0x79 */ 1, + /* i64_ctz = 0x7a */ 1, + /* i64_popcnt = 0x7b */ 1, + /* i64_add = 0x7c */ 1, + /* i64_sub = 0x7d */ 1, + /* i64_mul = 0x7e */ 1, + /* i64_div_s = 0x7f */ 1, + /* i64_div_u = 0x80 */ 1, + /* i64_rem_s = 0x81 */ 1, + /* i64_rem_u = 0x82 */ 1, + /* i64_and = 0x83 */ 1, + /* i64_or = 0x84 */ 1, + /* i64_xor = 0x85 */ 1, + /* i64_shl = 0x86 */ 1, + /* i64_shr_s = 0x87 */ 1, + /* i64_shr_u = 0x88 */ 1, + /* i64_rotl = 0x89 */ 1, + /* i64_rotr = 0x8a */ 1, + + /* f32_abs = 0x8b */ 1, + /* f32_neg = 0x8c */ 1, + /* f32_ceil = 0x8d */ 1, + /* f32_floor = 0x8e */ 1, + /* f32_trunc = 0x8f */ 1, + /* f32_nearest = 0x90 */ 1, + /* f32_sqrt = 0x91 */ 1, + /* f32_add = 0x92 */ 1, + /* f32_sub = 0x93 */ 1, + /* f32_mul = 0x94 */ 1, + /* f32_div = 0x95 */ 1, + /* f32_min = 0x96 */ 1, + /* f32_max = 0x97 */ 1, + /* f32_copysign = 0x98 */ 1, + + /* f64_abs = 0x99 */ 1, + /* f64_neg = 0x9a */ 1, + /* f64_ceil = 0x9b */ 1, + /* f64_floor = 0x9c */ 1, + /* f64_trunc = 0x9d */ 1, + /* f64_nearest = 0x9e */ 1, + /* f64_sqrt = 0x9f */ 1, + /* f64_add = 0xa0 */ 1, + /* f64_sub = 0xa1 */ 1, + /* f64_mul = 0xa2 */ 1, + /* f64_div = 0xa3 */ 1, + /* f64_min = 0xa4 */ 1, + /* f64_max = 0xa5 */ 1, + /* f64_copysign = 0xa6 */ 1, + + /* i32_wrap_i64 = 0xa7 */ 1, + /* i32_trunc_f32_s = 0xa8 */ 1, + /* i32_trunc_f32_u = 0xa9 */ 1, + /* i32_trunc_f64_s = 0xaa */ 1, + /* i32_trunc_f64_u = 0xab */ 1, + /* i64_extend_i32_s = 0xac */ 1, + /* i64_extend_i32_u = 0xad */ 1, + /* i64_trunc_f32_s = 0xae */ 1, + /* i64_trunc_f32_u = 0xaf */ 1, + /* i64_trunc_f64_s = 0xb0 */ 1, + /* i64_trunc_f64_u = 0xb1 */ 1, + /* f32_convert_i32_s = 0xb2 */ 1, + /* f32_convert_i32_u = 0xb3 */ 1, + /* f32_convert_i64_s = 0xb4 */ 1, + /* f32_convert_i64_u = 0xb5 */ 1, + /* f32_demote_f64 = 0xb6 */ 1, + /* f64_convert_i32_s = 0xb7 */ 1, + /* f64_convert_i32_u = 0xb8 */ 1, + /* f64_convert_i64_s = 0xb9 */ 1, + /* f64_convert_i64_u = 0xba */ 1, + /* f64_promote_f32 = 0xbb */ 1, + /* i32_reinterpret_f32 = 0xbc */ 1, + /* i64_reinterpret_f64 = 0xbd */ 1, + /* f32_reinterpret_i32 = 0xbe */ 1, + /* f64_reinterpret_i64 = 0xbf */ 1, +}; } // namespace const InstructionType* get_instruction_type_table() noexcept @@ -456,4 +672,8 @@ const uint8_t* get_instruction_max_align_table() noexcept return instruction_max_align_table; } +const int16_t* get_instruction_cost_table() noexcept +{ + return instruction_cost_table; +} } // namespace fizzy diff --git a/lib/fizzy/instructions.hpp b/lib/fizzy/instructions.hpp index 3dec5937e..a5f03d634 100644 --- a/lib/fizzy/instructions.hpp +++ b/lib/fizzy/instructions.hpp @@ -27,4 +27,8 @@ const InstructionType* get_instruction_type_table() noexcept; /// It may contain invalid value for instructions not needing it. const uint8_t* get_instruction_max_align_table() noexcept; +/// Returns the table of cost values for each instruction - how many ticks an instruction takes in +/// execution metering. +const int16_t* get_instruction_cost_table() noexcept; + } // namespace fizzy diff --git a/test/unittests/execute_test.cpp b/test/unittests/execute_test.cpp index 2c8f372cc..25108ee01 100644 --- a/test/unittests/execute_test.cpp +++ b/test/unittests/execute_test.cpp @@ -1074,3 +1074,30 @@ TEST(execute, stack_abuse) EXPECT_THAT(execute(parse(wasm), 0, {1000}), Result(1136)); } + +TEST(execute, metering) +{ + /* wat2wasm + (func (result i32) + i32.const 1 + ) + */ + const auto wasm = from_hex("0061736d010000000105016000017f030201000a0601040041010b"); + auto instance = instantiate(parse(wasm)); + + ExecutionContext ctx; + ctx.metering_enabled = true; + ctx.ticks = 100; + EXPECT_THAT(execute(*instance, 0, {}, ctx), Result(1_u32)); + EXPECT_EQ(ctx.ticks, 98); + + ctx.ticks = 2; + EXPECT_THAT(execute(*instance, 0, {}, ctx), Result(1_u32)); + EXPECT_EQ(ctx.ticks, 0); + + ctx.ticks = 1; + EXPECT_THAT(execute(*instance, 0, {}, ctx), Traps()); + + ctx.ticks = 0; + EXPECT_THAT(execute(*instance, 0, {}, ctx), Traps()); +}