swasm_utils/
gas.rs

1use std::vec::Vec;
2
3use swasm::{elements, builder};
4use rules;
5
6pub fn update_call_index(instructions: &mut elements::Instructions, inserted_index: u32) {
7	use swasm::elements::Instruction::*;
8	for instruction in instructions.elements_mut().iter_mut() {
9		if let &mut Call(ref mut call_index) = instruction {
10			if *call_index >= inserted_index { *call_index += 1}
11		}
12	}
13}
14
15/// A block of code represented by it's start position and cost.
16///
17/// The block typically starts with instructions such as `loop`, `block`, `if`, etc.
18///
19/// An example of block:
20///
21/// ```ignore
22/// loop
23///   i32.const 1
24///   get_local 0
25///   i32.sub
26///   tee_local 0
27///   br_if 0
28/// end
29/// ```
30///
31/// The start of the block is `i32.const 1`.
32///
33#[derive(Debug)]
34struct BlockEntry {
35	/// Index of the first instruction (aka `Opcode`) in the block.
36	start_pos: usize,
37	/// Sum of costs of all instructions until end of the block.
38	cost: u32,
39}
40
41struct Counter {
42	/// All blocks in the order of theirs start position.
43	blocks: Vec<BlockEntry>,
44
45	// Stack of blocks. Each element is an index to a `self.blocks` vector.
46	stack: Vec<usize>,
47}
48
49impl Counter {
50	fn new() -> Counter {
51		Counter {
52			stack: Vec::new(),
53			blocks: Vec::new(),
54		}
55	}
56
57	/// Begin a new block.
58	fn begin(&mut self, cursor: usize) {
59		let block_idx = self.blocks.len();
60		self.blocks.push(BlockEntry {
61			start_pos: cursor,
62			cost: 1,
63		});
64		self.stack.push(block_idx);
65	}
66
67	/// Finalize the current block.
68	///
69	/// Finalized blocks have final cost which will not change later.
70	fn finalize(&mut self) -> Result<(), ()> {
71		self.stack.pop().ok_or_else(|| ())?;
72		Ok(())
73	}
74
75	/// Increment the cost of the current block by the specified value.
76	fn increment(&mut self, val: u32) -> Result<(), ()> {
77		let stack_top = self.stack.last_mut().ok_or_else(|| ())?;
78		let top_block = self.blocks.get_mut(*stack_top).ok_or_else(|| ())?;
79
80		top_block.cost = top_block.cost.checked_add(val).ok_or_else(|| ())?;
81
82		Ok(())
83	}
84}
85
86fn inject_grow_counter(instructions: &mut elements::Instructions, grow_counter_func: u32) -> usize {
87	use swasm::elements::Instruction::*;
88	let mut counter = 0;
89	for instruction in instructions.elements_mut() {
90		if let GrowMemory(_) = *instruction {
91			*instruction = Call(grow_counter_func);
92			counter += 1;
93		}
94	}
95	counter
96}
97
98fn add_grow_counter(module: elements::Module, rules: &rules::Set, gas_func: u32) -> elements::Module {
99	use swasm::elements::Instruction::*;
100
101	let mut b = builder::from_module(module);
102	b.push_function(
103		builder::function()
104			.signature().params().i32().build().with_return_type(Some(elements::ValueType::I32)).build()
105			.body()
106				.with_instructions(elements::Instructions::new(vec![
107					GetLocal(0),
108					GetLocal(0),
109					I32Const(rules.grow_cost() as i32),
110					I32Mul,
111					// todo: there should be strong guarantee that it does not return anything on stack?
112					Call(gas_func),
113					GrowMemory(0),
114					End,
115				]))
116				.build()
117			.build()
118	);
119
120	b.build()
121}
122
123pub fn inject_counter(
124	instructions: &mut elements::Instructions,
125	rules: &rules::Set,
126	gas_func: u32,
127) -> Result<(), ()> {
128	use swasm::elements::Instruction::*;
129
130	let mut counter = Counter::new();
131
132	// Begin an implicit function (i.e. `func...end`) block.
133	counter.begin(0);
134
135	for cursor in 0..instructions.elements().len() {
136		let instruction = &instructions.elements()[cursor];
137		match *instruction {
138			Block(_) | If(_) | Loop(_) => {
139				// Increment previous block with the cost of the current opcode.
140				let instruction_cost = rules.process(instruction)?;
141				counter.increment(instruction_cost)?;
142
143				// Begin new block. The cost of the following opcodes until `End` or `Else` will
144				// be included into this block.
145				counter.begin(cursor + 1);
146			}
147			End => {
148				// Just finalize current block.
149				counter.finalize()?;
150			},
151			Else => {
152				// `Else` opcode is being encountered. So the case we are looking at:
153				//
154				// if
155				//   ...
156				// else <-- cursor
157				//   ...
158				// end
159				//
160				// Finalize the current block ('then' part of the if statement),
161				// and begin another one for the 'else' part.
162				counter.finalize()?;
163				counter.begin(cursor + 1);
164			}
165			_ => {
166				// An ordinal non control flow instruction. Just increment the cost of the current block.
167				let instruction_cost = rules.process(instruction)?;
168				counter.increment(instruction_cost)?;
169			}
170		}
171	}
172
173	// Then insert metering calls.
174	let mut cumulative_offset = 0;
175	for block in counter.blocks {
176		let effective_pos = block.start_pos + cumulative_offset;
177
178		instructions.elements_mut().insert(effective_pos, I32Const(block.cost as i32));
179		instructions.elements_mut().insert(effective_pos+1, Call(gas_func));
180
181		// Take into account these two inserted instructions.
182		cumulative_offset += 2;
183	}
184
185	Ok(())
186}
187
188/// Injects gas counter.
189///
190/// Can only fail if encounters operation forbidden by gas rules,
191/// in this case it returns error with the original module.
192pub fn inject_gas_counter(module: elements::Module, rules: &rules::Set)
193	-> Result<elements::Module, elements::Module>
194{
195	// Injecting gas counting external
196	let mut mbuilder = builder::from_module(module);
197	let import_sig = mbuilder.push_signature(
198		builder::signature()
199			.param().i32()
200			.build_sig()
201		);
202
203	mbuilder.push_import(
204		builder::import()
205			.module("env")
206			.field("gas")
207			.external().func(import_sig)
208			.build()
209		);
210
211	// back to plain module
212	let mut module = mbuilder.build();
213
214	// calculate actual function index of the imported definition
215	//    (substract all imports that are NOT functions)
216
217	let gas_func = module.import_count(elements::ImportCountType::Function) as u32 - 1;
218	let total_func = module.functions_space() as u32;
219	let mut need_grow_counter = false;
220	let mut error = false;
221
222	// Updating calling addresses (all calls to function index >= `gas_func` should be incremented)
223	for section in module.sections_mut() {
224		match section {
225			&mut elements::Section::Code(ref mut code_section) => {
226				for ref mut func_body in code_section.bodies_mut() {
227					update_call_index(func_body.code_mut(), gas_func);
228					if let Err(_) = inject_counter(func_body.code_mut(), rules, gas_func) {
229						error = true;
230						break;
231					}
232					if rules.grow_cost() > 0 {
233						if inject_grow_counter(func_body.code_mut(), total_func) > 0 {
234							need_grow_counter = true;
235						}
236					}
237				}
238			},
239			&mut elements::Section::Export(ref mut export_section) => {
240				for ref mut export in export_section.entries_mut() {
241					if let &mut elements::Internal::Function(ref mut func_index) = export.internal_mut() {
242						if *func_index >= gas_func { *func_index += 1}
243					}
244				}
245			},
246			&mut elements::Section::Element(ref mut elements_section) => {
247				for ref mut segment in elements_section.entries_mut() {
248					// update all indirect call addresses initial values
249					for func_index in segment.members_mut() {
250						if *func_index >= gas_func { *func_index += 1}
251					}
252				}
253			},
254			&mut elements::Section::Start(ref mut start_idx) => {
255				if *start_idx >= gas_func { *start_idx += 1}
256			},
257			_ => { }
258		}
259	}
260
261	if error { return Err(module); }
262
263	if need_grow_counter { Ok(add_grow_counter(module, rules, gas_func)) } else { Ok(module) }
264}
265
266#[cfg(test)]
267mod tests {
268
269	extern crate wabt;
270
271	use swasm::{serialize, builder, elements};
272	use super::*;
273	use rules;
274
275	#[test]
276	fn simple_grow() {
277		use swasm::elements::Instruction::*;
278
279		let module = builder::module()
280			.global()
281				.value_type().i32()
282				.build()
283			.function()
284				.signature().param().i32().build()
285				.body()
286					.with_instructions(elements::Instructions::new(
287						vec![
288							GetGlobal(0),
289							GrowMemory(0),
290							End
291						]
292					))
293					.build()
294				.build()
295			.build();
296
297		let injected_module = inject_gas_counter(module, &rules::Set::default().with_grow_cost(10000)).unwrap();
298
299		assert_eq!(
300			&vec![
301				I32Const(3),
302				Call(0),
303				GetGlobal(0),
304				Call(2),
305				End
306			][..],
307			injected_module
308				.code_section().expect("function section should exist").bodies()[0]
309				.code().elements()
310		);
311		assert_eq!(
312			&vec![
313				GetLocal(0),
314				GetLocal(0),
315				I32Const(10000),
316				I32Mul,
317				Call(0),
318				GrowMemory(0),
319				End,
320			][..],
321			injected_module
322				.code_section().expect("function section should exist").bodies()[1]
323				.code().elements()
324		);
325
326		let binary = serialize(injected_module).expect("serialization failed");
327		self::wabt::swasm2wat(&binary).unwrap();
328	}
329
330	#[test]
331	fn grow_no_gas_no_track() {
332		use swasm::elements::Instruction::*;
333
334		let module = builder::module()
335			.global()
336				.value_type().i32()
337				.build()
338			.function()
339				.signature().param().i32().build()
340				.body()
341					.with_instructions(elements::Instructions::new(
342						vec![
343							GetGlobal(0),
344							GrowMemory(0),
345							End
346						]
347					))
348					.build()
349				.build()
350			.build();
351
352		let injected_module = inject_gas_counter(module, &rules::Set::default()).unwrap();
353
354		assert_eq!(
355			&vec![
356				I32Const(3),
357				Call(0),
358				GetGlobal(0),
359				GrowMemory(0),
360				End
361			][..],
362			injected_module
363				.code_section().expect("function section should exist").bodies()[0]
364				.code().elements()
365		);
366
367		assert_eq!(injected_module.functions_space(), 2);
368
369		let binary = serialize(injected_module).expect("serialization failed");
370		self::wabt::swasm2wat(&binary).unwrap();
371	}
372
373	#[test]
374	fn simple() {
375		use swasm::elements::Instruction::*;
376
377		let module = builder::module()
378			.global()
379				.value_type().i32()
380				.build()
381			.function()
382				.signature().param().i32().build()
383				.body()
384					.with_instructions(elements::Instructions::new(
385						vec![
386							GetGlobal(0),
387							End
388						]
389					))
390					.build()
391				.build()
392			.build();
393
394		let injected_module = inject_gas_counter(module, &Default::default()).unwrap();
395
396		assert_eq!(
397			&vec![
398				I32Const(2),
399				Call(0),
400				GetGlobal(0),
401				End
402			][..],
403			injected_module
404				.code_section().expect("function section should exist").bodies()[0]
405				.code().elements()
406		);
407	}
408
409	#[test]
410	fn nested() {
411		use swasm::elements::Instruction::*;
412
413		let module = builder::module()
414			.global()
415				.value_type().i32()
416				.build()
417			.function()
418				.signature().param().i32().build()
419				.body()
420					.with_instructions(elements::Instructions::new(
421						vec![
422							GetGlobal(0),
423							Block(elements::BlockType::NoResult),
424								GetGlobal(0),
425								GetGlobal(0),
426								GetGlobal(0),
427							End,
428							GetGlobal(0),
429							End
430						]
431					))
432					.build()
433				.build()
434			.build();
435
436		let injected_module = inject_gas_counter(module, &Default::default()).unwrap();
437
438		assert_eq!(
439			&vec![
440				I32Const(4),
441				Call(0),
442				GetGlobal(0),
443				Block(elements::BlockType::NoResult),
444					I32Const(4),
445					Call(0),
446					GetGlobal(0),
447					GetGlobal(0),
448					GetGlobal(0),
449				End,
450				GetGlobal(0),
451				End
452			][..],
453			injected_module
454				.code_section().expect("function section should exist").bodies()[0]
455				.code().elements()
456		);
457	}
458
459	#[test]
460	fn ifelse() {
461		use swasm::elements::Instruction::*;
462
463		let module = builder::module()
464			.global()
465				.value_type().i32()
466				.build()
467			.function()
468				.signature().param().i32().build()
469				.body()
470					.with_instructions(elements::Instructions::new(
471						vec![
472							GetGlobal(0),
473							If(elements::BlockType::NoResult),
474								GetGlobal(0),
475								GetGlobal(0),
476								GetGlobal(0),
477							Else,
478								GetGlobal(0),
479								GetGlobal(0),
480							End,
481							GetGlobal(0),
482							End
483						]
484					))
485					.build()
486				.build()
487			.build();
488
489		let injected_module = inject_gas_counter(module, &Default::default()).unwrap();
490
491		assert_eq!(
492			&vec![
493				I32Const(4),
494				Call(0),
495				GetGlobal(0),
496				If(elements::BlockType::NoResult),
497					I32Const(4),
498					Call(0),
499					GetGlobal(0),
500					GetGlobal(0),
501					GetGlobal(0),
502				Else,
503					I32Const(3),
504					Call(0),
505					GetGlobal(0),
506					GetGlobal(0),
507				End,
508				GetGlobal(0),
509				End
510			][..],
511			injected_module
512				.code_section().expect("function section should exist").bodies()[0]
513				.code().elements()
514		);
515	}
516
517	#[test]
518	fn call_index() {
519		use swasm::elements::Instruction::*;
520
521		let module = builder::module()
522			.global()
523				.value_type().i32()
524				.build()
525			.function()
526				.signature().param().i32().build()
527				.body().build()
528				.build()
529			.function()
530				.signature().param().i32().build()
531				.body()
532					.with_instructions(elements::Instructions::new(
533						vec![
534							Call(0),
535							If(elements::BlockType::NoResult),
536								Call(0),
537								Call(0),
538								Call(0),
539							Else,
540								Call(0),
541								Call(0),
542							End,
543							Call(0),
544							End
545						]
546					))
547					.build()
548				.build()
549			.build();
550
551		let injected_module = inject_gas_counter(module, &Default::default()).unwrap();
552
553		assert_eq!(
554			&vec![
555				I32Const(4),
556				Call(0),
557				Call(1),
558				If(elements::BlockType::NoResult),
559					I32Const(4),
560					Call(0),
561					Call(1),
562					Call(1),
563					Call(1),
564				Else,
565					I32Const(3),
566					Call(0),
567					Call(1),
568					Call(1),
569				End,
570				Call(1),
571				End
572			][..],
573			injected_module
574				.code_section().expect("function section should exist").bodies()[1]
575				.code().elements()
576		);
577	}
578
579
580	#[test]
581	fn forbidden() {
582		use swasm::elements::Instruction::*;
583
584		let module = builder::module()
585			.global()
586				.value_type().i32()
587				.build()
588			.function()
589				.signature().param().i32().build()
590				.body()
591					.with_instructions(elements::Instructions::new(
592						vec![
593							F32Const(555555),
594							End
595						]
596					))
597					.build()
598				.build()
599			.build();
600
601		let rules = rules::Set::default().with_forbidden_floats();
602
603		if let Err(_) = inject_gas_counter(module, &rules) { }
604		else { panic!("Should be error because of the forbidden operation")}
605
606	}
607
608}