Skip to content

Commit 022afd4

Browse files
authored
wasm-smith: Consume fuel for implicit loops (#2519)
This commit adds fuel consumption for instructions with implicit loops (such as `array.copy`) to the `ensure_termination` mode. Part of bytecodealliance/wasmtime#11427
1 parent 649c4ea commit 022afd4

1 file changed

Lines changed: 172 additions & 8 deletions

File tree

crates/wasm-smith/src/core/terminate.rs

Lines changed: 172 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,33 @@ impl Module {
2828
self.defined_globals
2929
.push((fuel_global, ConstExpr::i32_const(default_fuel as i32)));
3030

31-
for code in &mut self.code {
32-
let check_fuel = |insts: &mut Vec<Instruction>| {
31+
let defined_funcs = &self.funcs[self.funcs.len() - self.code.len()..];
32+
for ((_, ty), code) in defined_funcs.iter().zip(self.code.iter_mut()) {
33+
let params = ty.params.len();
34+
35+
let mut temp_i32_local = None;
36+
let mut temp_i32_local = |locals: &mut Vec<_>| {
37+
if let Some(l) = temp_i32_local {
38+
return l;
39+
}
40+
let l = u32::try_from(locals.len() + params).unwrap();
41+
locals.push(ValType::I32);
42+
temp_i32_local = Some(l);
43+
l
44+
};
45+
46+
let mut temp_i64_local = None;
47+
let mut temp_i64_local = |locals: &mut Vec<_>| {
48+
if let Some(l) = temp_i64_local {
49+
return l;
50+
}
51+
let l = u32::try_from(locals.len() + params).unwrap();
52+
locals.push(ValType::I64);
53+
temp_i64_local = Some(l);
54+
l
55+
};
56+
57+
let check_fuel_1 = |insts: &mut Vec<Instruction>| {
3358
// if fuel == 0 { trap }
3459
insts.push(Instruction::GlobalGet(fuel_global));
3560
insts.push(Instruction::I32Eqz);
@@ -44,6 +69,44 @@ impl Module {
4469
insts.push(Instruction::GlobalSet(fuel_global));
4570
};
4671

72+
let check_fuel_n = |insts: &mut Vec<Instruction>, local: u32| {
73+
// if local >= fuel { trap }
74+
insts.push(Instruction::LocalGet(local));
75+
insts.push(Instruction::GlobalGet(fuel_global));
76+
insts.push(Instruction::I32GeU);
77+
insts.push(Instruction::If(BlockType::Empty));
78+
insts.push(Instruction::Unreachable);
79+
insts.push(Instruction::End);
80+
// fuel -= local
81+
insts.push(Instruction::GlobalGet(fuel_global));
82+
insts.push(Instruction::LocalGet(local));
83+
insts.push(Instruction::I32Sub);
84+
insts.push(Instruction::GlobalSet(fuel_global));
85+
};
86+
87+
let check_fuel_32_or_64 = |locals: &mut Vec<ValType>,
88+
temp_i32_local: &mut dyn FnMut(&mut Vec<ValType>) -> u32,
89+
temp_i64_local: &mut dyn FnMut(&mut Vec<ValType>) -> u32,
90+
new_insts: &mut Vec<Instruction>,
91+
inst: Instruction,
92+
is_64: bool| {
93+
if is_64 {
94+
let local64 = temp_i64_local(locals);
95+
let local32 = temp_i32_local(locals);
96+
new_insts.push(Instruction::LocalTee(local64));
97+
new_insts.push(Instruction::I32WrapI64);
98+
new_insts.push(Instruction::LocalSet(local32));
99+
check_fuel_n(new_insts, local32);
100+
new_insts.push(Instruction::LocalGet(local64));
101+
new_insts.push(inst);
102+
} else {
103+
let local = temp_i32_local(locals);
104+
new_insts.push(Instruction::LocalTee(local));
105+
check_fuel_n(new_insts, local);
106+
new_insts.push(inst);
107+
}
108+
};
109+
47110
let instrs = match &mut code.instructions {
48111
Instructions::Generated(list) => list,
49112
Instructions::Arbitrary(_) => {
@@ -57,15 +120,116 @@ impl Module {
57120

58121
// Check fuel at the start of functions to deal with infinite
59122
// recursion.
60-
check_fuel(&mut new_insts);
123+
check_fuel_1(&mut new_insts);
61124

62125
for inst in mem::replace(instrs, vec![]) {
63-
let is_loop = matches!(&inst, Instruction::Loop(_));
64-
new_insts.push(inst);
126+
match &inst {
127+
// Check fuel at loop heads to deal with infinite loops.
128+
Instruction::Loop(_) => {
129+
new_insts.push(inst);
130+
check_fuel_1(&mut new_insts);
131+
}
132+
133+
// Check fuel on instructions that imply loops and decrement
134+
// fuel accordingly and always have `len: i32` on top of the
135+
// stack.
136+
Instruction::ArrayCopy { .. }
137+
| Instruction::ArrayFill(_)
138+
| Instruction::ArrayInitData { .. }
139+
| Instruction::ArrayInitElem { .. }
140+
| Instruction::ArrayNew(_)
141+
| Instruction::ArrayNewDefault(_)
142+
| Instruction::ArrayNewData { .. }
143+
| Instruction::ArrayNewElem { .. } => {
144+
let local = temp_i32_local(&mut code.locals);
145+
new_insts.push(Instruction::LocalTee(local));
146+
check_fuel_n(&mut new_insts, local);
147+
new_insts.push(inst);
148+
}
149+
150+
// Check fuel on `table.init`, whose `len` operand is always
151+
// `i32`, even for `table64`.
152+
Instruction::TableInit { .. } => {
153+
let local = temp_i32_local(&mut code.locals);
154+
new_insts.push(Instruction::LocalTee(local));
155+
check_fuel_n(&mut new_insts, local);
156+
new_insts.push(inst);
157+
}
158+
159+
// Check fuel on `table.fill`, whose `len` operand has the
160+
// table's index type.
161+
Instruction::TableFill(table) => {
162+
let table = usize::try_from(*table).unwrap();
163+
let is_64 = self.tables[table].table64;
164+
check_fuel_32_or_64(
165+
&mut code.locals,
166+
&mut temp_i32_local,
167+
&mut temp_i64_local,
168+
&mut new_insts,
169+
inst,
170+
is_64,
171+
);
172+
}
173+
174+
// Check fuel on `table.copy`, whose `len` operand has the
175+
// smaller of the source and destination tables' index types.
176+
Instruction::TableCopy {
177+
dst_table,
178+
src_table,
179+
} => {
180+
let dst_table = usize::try_from(*dst_table).unwrap();
181+
let src_table = usize::try_from(*src_table).unwrap();
182+
check_fuel_32_or_64(
183+
&mut code.locals,
184+
&mut temp_i32_local,
185+
&mut temp_i64_local,
186+
&mut new_insts,
187+
inst,
188+
self.tables[dst_table].table64 && self.tables[src_table].table64,
189+
);
190+
}
191+
192+
// Check fuel on `memory.init`, whose `len` operand is
193+
// always `i32`, even for `memory64`.
194+
Instruction::MemoryInit { .. } => {
195+
let local = temp_i32_local(&mut code.locals);
196+
new_insts.push(Instruction::LocalTee(local));
197+
check_fuel_n(&mut new_insts, local);
198+
new_insts.push(inst);
199+
}
200+
201+
// Check fuel on `memory.fill`, whose `len` operand has the
202+
// memory's index type.
203+
Instruction::MemoryFill(mem) => {
204+
let mem = usize::try_from(*mem).unwrap();
205+
check_fuel_32_or_64(
206+
&mut code.locals,
207+
&mut temp_i32_local,
208+
&mut temp_i64_local,
209+
&mut new_insts,
210+
inst,
211+
self.memories[mem].memory64,
212+
);
213+
}
214+
215+
// Check fuel on `memory.copy`, whose `len` operand has the
216+
// smaller of the source and destination memories' index
217+
// types.
218+
Instruction::MemoryCopy { dst_mem, src_mem } => {
219+
let dst_mem = usize::try_from(*dst_mem).unwrap();
220+
let src_mem = usize::try_from(*src_mem).unwrap();
221+
check_fuel_32_or_64(
222+
&mut code.locals,
223+
&mut temp_i32_local,
224+
&mut temp_i64_local,
225+
&mut new_insts,
226+
inst,
227+
self.memories[dst_mem].memory64 && self.memories[src_mem].memory64,
228+
);
229+
}
65230

66-
// Check fuel at loop heads to deal with infinite loops.
67-
if is_loop {
68-
check_fuel(&mut new_insts);
231+
// Otherwise, just keep the instruction.
232+
_ => new_insts.push(inst),
69233
}
70234
}
71235

0 commit comments

Comments
 (0)