diff --git a/wasm-utils/cli/gas/main.rs b/wasm-utils/cli/gas/main.rs index 7dab30c..405ea72 100644 --- a/wasm-utils/cli/gas/main.rs +++ b/wasm-utils/cli/gas/main.rs @@ -15,9 +15,12 @@ fn main() { let memory_page_cost = 256 * 1024; // 256k gas for 1 page (64k) of memory + // let config = pwasm_utils::rules::Set::default() + // .with_forbidden_floats() // Reject floating point opreations. + // .with_grow_cost(memory_page_cost); + let config = pwasm_utils::rules::Set::default() - .with_forbidden_floats() // Reject floating point opreations. - .with_grow_cost(memory_page_cost); + .with_forbidden_floats(); // Loading module let module = parity_wasm::deserialize_file(&args[1]).expect("Module deserialization to succeed"); diff --git a/wasm-utils/src/gas.rs b/wasm-utils/src/gas.rs index 322389a..5aa1b00 100644 --- a/wasm-utils/src/gas.rs +++ b/wasm-utils/src/gas.rs @@ -36,6 +36,7 @@ struct BlockEntry { start_pos: usize, /// Sum of costs of all instructions until end of the block. cost: u32, + flow_up: bool, } struct Counter { @@ -55,11 +56,12 @@ impl Counter { } /// Begin a new block. - fn begin(&mut self, cursor: usize) { + fn begin(&mut self, cursor: usize, cost: u32, flow_up: bool) { let block_idx = self.blocks.len(); self.blocks.push(BlockEntry { start_pos: cursor, - cost: 1, + cost: cost, + flow_up: flow_up, }); self.stack.push(block_idx); } @@ -72,15 +74,52 @@ impl Counter { Ok(()) } - /// Increment the cost of the current block by the specified value. - fn increment(&mut self, val: u32) -> Result<(), ()> { - let stack_top = self.stack.last_mut().ok_or_else(|| ())?; - let top_block = self.blocks.get_mut(*stack_top).ok_or_else(|| ())?; - - top_block.cost = top_block.cost.checked_add(val).ok_or_else(|| ())?; - + fn increment_control_flow(&mut self, val: u32) -> Result<(), ()> { + /* + ;; if the current block and parent block has 0 cost, then we're seeing a sequence of nested blocks + + (block $B1 + (block $B2 + (block $B3 + ... + ))) + + ;; instead of calling to useGas once after each block, we can sum up the 1 gas per `block` instructions + ;; and charge for all three at the top of block $B1 + + ;; instead of this: + (block $B1 + (call useGas (i32.const 1)) + (block $B2 + (call useGas (i32.const 1)) + (block $B3 + (call useGas (i32.const 1)) + ... + ))) + + ;; do this: + (block $B1 + (call useGas (i32.const 3)) + (block $B2 + (block $B3 + ... + ))) + */ + + // find closest ancestor block (starting from top of stack and going down) with blocked flow and add 1 + + for (_i, stack_i) in self.stack.iter().rev().enumerate() { + let block_i = self.blocks.get_mut(*stack_i).ok_or_else(|| ())?; + if !block_i.flow_up || *stack_i == 0 { + block_i.cost = block_i.cost.checked_add(val).ok_or_else(|| ())?; + //println!("found ancestor with blocked flow or no parent. incrementing to new cost: {:?} and returning...", block_i.cost); + break; + } + } Ok(()) + } + } fn inject_grow_counter(instructions: &mut elements::Instructions, grow_counter_func: u32) -> usize { @@ -130,23 +169,78 @@ pub fn inject_counter( let mut counter = Counter::new(); // Begin an implicit function (i.e. `func...end`) block. - counter.begin(0); + counter.begin(0, 0, false); for cursor in 0..instructions.elements().len() { let instruction = &instructions.elements()[cursor]; match *instruction { - Block(_) | If(_) | Loop(_) => { + Block(_) => { // Increment previous block with the cost of the current opcode. let instruction_cost = rules.process(instruction)?; - counter.increment(instruction_cost)?; + //counter.increment(instruction_cost)?; // Begin new block. The cost of the following opcodes until `End` or `Else` will // be included into this block. - counter.begin(cursor + 1); - } + + // add cost, which may flow up to ancestor block + counter.increment_control_flow(instruction_cost)?; + + counter.begin(cursor + 1, 0, true); + + }, + If(_) => { + // Increment previous block with the cost of the current opcode. + let instruction_cost = rules.process(instruction)?; + //counter.increment(instruction_cost)?; + counter.increment_control_flow(instruction_cost)?; + + // begin If with cost 1, to force new costs added to top of block + counter.begin(cursor + 1, 0, false); + }, + BrIf(_) => { + // Increment previous block with the cost of the current opcode. + let instruction_cost = rules.process(instruction)?; + //counter.increment(instruction_cost)?; + counter.increment_control_flow(instruction_cost)?; + + // on a br_if, we finalize the previous block because those instructions will always be executed. + // intructions after the if will be executed conditionally, so we start a new block so that gasUsed can be called after the if. + counter.finalize()?; + + // begin If with cost 1, to force new costs added to top of block + counter.begin(cursor + 1, 0, false); + }, + Loop(_) => { + let instruction_cost = rules.process(instruction)?; + //counter.increment(instruction_cost)?; + //counter.increment_control_flow(instruction_cost)?; + + counter.begin(cursor + 1, 0, false); + // charge for the loop after the loop instruction + // need to do this because the loop could be executed many times (the br_if that jumps to the loop is a separate instruction and gas charge) + counter.increment_control_flow(instruction_cost)?; + }, + Br(_) => { + // anything after a break is dead code. + // for now, we treat dead code blocks like any other (the metering will not be executed) + // TODO: handle properly and don't inject metering inside dead code blocks + let instruction_cost = rules.process(instruction)?; + counter.increment_control_flow(instruction_cost)?; + counter.finalize()?; + counter.begin(cursor + 1, 0, false); + }, + // br_table is always followed by end (in the ecmul wasm code, at least) + // BrTable(_,_) => { }, + // return is always followed by end (in the ecmul wasm code, at least) + // Return => { }, End => { // Just finalize current block. + //counter.increment_control_flow(instruction_cost)?; + // wasabi doesn't count end as an instruction, so neither will we (no gas charge) + + counter.finalize()?; + counter.begin(cursor + 1, 0, false); }, Else => { // `Else` opcode is being encountered. So the case we are looking at: @@ -160,12 +254,16 @@ pub fn inject_counter( // Finalize the current block ('then' part of the if statement), // and begin another one for the 'else' part. counter.finalize()?; - counter.begin(cursor + 1); - } + counter.begin(cursor + 1, 1, false); + }, + Unreachable => { + // charge nothing, do nothing + }, _ => { // An ordinal non control flow instruction. Just increment the cost of the current block. let instruction_cost = rules.process(instruction)?; - counter.increment(instruction_cost)?; + //counter.increment(instruction_cost)?; + counter.increment_control_flow(instruction_cost)?; } } } @@ -173,13 +271,15 @@ pub fn inject_counter( // Then insert metering calls. let mut cumulative_offset = 0; for block in counter.blocks { - let effective_pos = block.start_pos + cumulative_offset; + if block.cost > 0 { + let effective_pos = block.start_pos + cumulative_offset; - instructions.elements_mut().insert(effective_pos, I64Const(block.cost as i64)); - instructions.elements_mut().insert(effective_pos+1, Call(gas_func)); + instructions.elements_mut().insert(effective_pos, I64Const(block.cost as i64)); + instructions.elements_mut().insert(effective_pos+1, Call(gas_func)); - // Take into account these two inserted instructions. - cumulative_offset += 2; + // Take into account these two inserted instructions. + cumulative_offset += 2; + } } Ok(())