Coverage Report

Created: 2025-12-09 07:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wasmtime/fuzz/fuzz_targets/cranelift-icache.rs
Line
Count
Source
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
8.55k
    pub fn generate(u: &mut Unstructured) -> anyhow::Result<Self> {
47
8.55k
        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
8.55k
        let target = u.choose(isa::ALL_ARCHITECTURES)?;
54
8.55k
        let mut builder =
55
8.55k
            isa::lookup_by_name(target).map_err(|_| arbitrary::Error::IncorrectFormat)?;
56
8.55k
        let architecture = builder.triple().architecture;
57
58
8.55k
        let mut generator = FuzzGen::new(u);
59
8.55k
        let flags = generator
60
8.55k
            .generate_flags(architecture)
61
8.55k
            .map_err(|_| arbitrary::Error::IncorrectFormat)?;
62
8.55k
        generator.set_isa_flags(&mut builder, IsaFlagGen::All)?;
63
8.55k
        let isa = builder
64
8.55k
            .finish(flags)
65
8.55k
            .map_err(|_| arbitrary::Error::IncorrectFormat)?;
66
67
        // Function name must be in a different namespace than TESTFILE_NAMESPACE (0)
68
8.55k
        let fname = UserFuncName::user(1, 0);
69
70
        // We don't actually generate these functions, we just simulate their signatures and names
71
8.55k
        let func_count = generator
72
8.55k
            .u
73
8.55k
            .int_in_range(generator.config.testcase_funcs.clone())?;
74
8.55k
        let usercalls = (0..func_count)
75
17.2k
            .map(|i| {
76
17.2k
                let name = UserExternalName::new(2, i as u32);
77
17.2k
                let sig = generator.generate_signature(&*isa)?;
78
17.2k
                Ok((name, sig))
79
17.2k
            })
80
8.55k
            .collect::<anyhow::Result<Vec<(UserExternalName, Signature)>>>()
81
8.55k
            .map_err(|_| arbitrary::Error::IncorrectFormat)?;
82
83
8.55k
        let func = generator
84
8.55k
            .generate_func(fname, isa.clone(), usercalls, ALLOWED_LIBCALLS.to_vec())
85
8.55k
            .map_err(|_| arbitrary::Error::IncorrectFormat)?;
86
87
7.73k
        Ok(FunctionWithIsa { isa, func })
88
8.55k
    }
89
}
90
91
impl fmt::Debug for FunctionWithIsa {
92
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93
        // TODO: We could avoid the clone here.
94
0
        let funcs = &[self.func.clone()];
95
0
        PrintableTestCase::compile(&self.isa, funcs).fmt(f)
96
0
    }
97
}
98
99
impl<'a> Arbitrary<'a> for FunctionWithIsa {
100
8.55k
    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
101
8.55k
        Self::generate(u).map_err(|_| arbitrary::Error::IncorrectFormat)
102
8.55k
    }
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
7.73k
        func.stencil.dfg.ext_funcs.values().find_map(|data| {
132
7.73k
            if let ExternalName::User(user_ext_ref) = &data.name {
133
7.73k
                Some(user_ext_ref)
134
            } else {
135
0
                None
136
            }
137
7.73k
        }) {
138
        let mut prev = func.params.user_named_funcs()[*user_ext_ref].clone();
139
0
        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
0
                        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
});