Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/fuzz/fuzz_targets/cranelift-icache.rs
1690 views
1
#![no_main]
2
3
use cranelift_codegen::{
4
Context,
5
cursor::{Cursor, FuncCursor},
6
incremental_cache as icache,
7
ir::{
8
self, ExternalName, Function, LibCall, Signature, UserExternalName, UserFuncName,
9
immediates::Imm64,
10
},
11
isa,
12
};
13
use libfuzzer_sys::{
14
arbitrary::{self, Arbitrary, Unstructured},
15
fuzz_target,
16
};
17
use std::fmt;
18
19
use cranelift_fuzzgen::*;
20
21
/// TODO: This *almost* could be replaced with `LibCall::all()`, but
22
/// `LibCall::signature` panics for some libcalls, so we need to avoid that.
23
const ALLOWED_LIBCALLS: &'static [LibCall] = &[
24
LibCall::CeilF32,
25
LibCall::CeilF64,
26
LibCall::FloorF32,
27
LibCall::FloorF64,
28
LibCall::TruncF32,
29
LibCall::TruncF64,
30
LibCall::NearestF32,
31
LibCall::NearestF64,
32
LibCall::FmaF32,
33
LibCall::FmaF64,
34
];
35
36
/// A generated function with an ISA that targets one of cranelift's backends.
37
pub struct FunctionWithIsa {
38
/// TargetIsa to use when compiling this test case
39
pub isa: isa::OwnedTargetIsa,
40
41
/// Function under test
42
pub func: Function,
43
}
44
45
impl FunctionWithIsa {
46
pub fn generate(u: &mut Unstructured) -> anyhow::Result<Self> {
47
let _ = env_logger::try_init();
48
49
// We filter out targets that aren't supported in the current build
50
// configuration after randomly choosing one, instead of randomly choosing
51
// a supported one, so that the same fuzz input works across different build
52
// configurations.
53
let target = u.choose(isa::ALL_ARCHITECTURES)?;
54
let mut builder =
55
isa::lookup_by_name(target).map_err(|_| arbitrary::Error::IncorrectFormat)?;
56
let architecture = builder.triple().architecture;
57
58
let mut generator = FuzzGen::new(u);
59
let flags = generator
60
.generate_flags(architecture)
61
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
62
generator.set_isa_flags(&mut builder, IsaFlagGen::All)?;
63
let isa = builder
64
.finish(flags)
65
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
66
67
// Function name must be in a different namespace than TESTFILE_NAMESPACE (0)
68
let fname = UserFuncName::user(1, 0);
69
70
// We don't actually generate these functions, we just simulate their signatures and names
71
let func_count = generator
72
.u
73
.int_in_range(generator.config.testcase_funcs.clone())?;
74
let usercalls = (0..func_count)
75
.map(|i| {
76
let name = UserExternalName::new(2, i as u32);
77
let sig = generator.generate_signature(&*isa)?;
78
Ok((name, sig))
79
})
80
.collect::<anyhow::Result<Vec<(UserExternalName, Signature)>>>()
81
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
82
83
let func = generator
84
.generate_func(fname, isa.clone(), usercalls, ALLOWED_LIBCALLS.to_vec())
85
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
86
87
Ok(FunctionWithIsa { isa, func })
88
}
89
}
90
91
impl fmt::Debug for FunctionWithIsa {
92
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93
// TODO: We could avoid the clone here.
94
let funcs = &[self.func.clone()];
95
PrintableTestCase::compile(&self.isa, funcs).fmt(f)
96
}
97
}
98
99
impl<'a> Arbitrary<'a> for FunctionWithIsa {
100
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
101
Self::generate(u).map_err(|_| arbitrary::Error::IncorrectFormat)
102
}
103
}
104
105
fuzz_target!(|func: FunctionWithIsa| {
106
let FunctionWithIsa { mut func, isa } = func;
107
108
let cache_key_hash = icache::compute_cache_key(&*isa, &func);
109
110
let mut context = Context::for_function(func.clone());
111
let prev_stencil = match context.compile_stencil(&*isa, &mut Default::default()) {
112
Ok(stencil) => stencil,
113
Err(_) => return,
114
};
115
116
let (prev_stencil, serialized) = icache::serialize_compiled(prev_stencil);
117
let serialized = serialized.expect("serialization should work");
118
let prev_result = prev_stencil.apply_params(&func.params);
119
120
let new_result = icache::try_finish_recompile(&func, &serialized)
121
.expect("recompilation should always work for identity");
122
123
assert_eq!(new_result, prev_result, "MachCompileResult:s don't match");
124
125
let new_info = new_result.code_info();
126
assert_eq!(new_info, prev_result.code_info(), "CodeInfo:s don't match");
127
128
// If the func has at least one user-defined func ref, change it to match a
129
// different external function.
130
let expect_cache_hit = if let Some(user_ext_ref) =
131
func.stencil.dfg.ext_funcs.values().find_map(|data| {
132
if let ExternalName::User(user_ext_ref) = &data.name {
133
Some(user_ext_ref)
134
} else {
135
None
136
}
137
}) {
138
let mut prev = func.params.user_named_funcs()[*user_ext_ref].clone();
139
prev.index = prev.index.checked_add(1).unwrap_or_else(|| prev.index - 1);
140
func.params.reset_user_func_name(*user_ext_ref, prev);
141
true
142
} else {
143
// otherwise just randomly change one instruction in the middle and see what happens.
144
let mut changed = false;
145
let mut cursor = FuncCursor::new(&mut func);
146
'out: while let Some(_block) = cursor.next_block() {
147
while let Some(inst) = cursor.next_inst() {
148
// It's impractical to do any replacement at this point. Try to find any
149
// instruction that returns one int value, and replace it with an iconst.
150
if cursor.func.dfg.inst_results(inst).len() != 1 {
151
continue;
152
}
153
let out_ty = cursor
154
.func
155
.dfg
156
.value_type(cursor.func.dfg.first_result(inst));
157
match out_ty {
158
ir::types::I32 | ir::types::I64 => {}
159
_ => continue,
160
}
161
162
if let ir::InstructionData::UnaryImm {
163
opcode: ir::Opcode::Iconst,
164
imm,
165
} = cursor.func.dfg.insts[inst]
166
{
167
let imm = imm.bits();
168
cursor.func.dfg.insts[inst] = ir::InstructionData::UnaryImm {
169
opcode: ir::Opcode::Iconst,
170
imm: Imm64::new(imm.checked_add(1).unwrap_or_else(|| imm - 1)),
171
};
172
} else {
173
cursor.func.dfg.insts[inst] = ir::InstructionData::UnaryImm {
174
opcode: ir::Opcode::Iconst,
175
imm: Imm64::new(42),
176
};
177
}
178
179
changed = true;
180
break 'out;
181
}
182
}
183
184
if !changed {
185
return;
186
}
187
188
// We made it so that there shouldn't be a cache hit.
189
false
190
};
191
192
let new_cache_key_hash = icache::compute_cache_key(&*isa, &func);
193
194
if expect_cache_hit {
195
assert!(cache_key_hash == new_cache_key_hash);
196
} else {
197
assert!(cache_key_hash != new_cache_key_hash);
198
}
199
200
context = Context::for_function(func.clone());
201
202
let after_mutation_result = match context.compile(&*isa, &mut Default::default()) {
203
Ok(info) => info,
204
Err(_) => return,
205
};
206
207
if expect_cache_hit {
208
let after_mutation_result_from_cache = icache::try_finish_recompile(&func, &serialized)
209
.expect("recompilation should always work for identity");
210
assert_eq!(*after_mutation_result, after_mutation_result_from_cache);
211
212
let new_info = after_mutation_result_from_cache.code_info();
213
assert_eq!(
214
new_info,
215
after_mutation_result.code_info(),
216
"CodeInfo:s don't match"
217
);
218
}
219
});
220
221